/************************************************************************
@NAME After-Effects Karaoke Framework
@VERSION 0.65a
@AUTHOR pichu
@License LGPL

    This script accompanies Adobe After-Effects CS3 and above

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published 
    by the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WzARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

	Adobe and After-Effects are the registered trademarks of Adobe Corporation.

@DESCRIPTION
The purpose of this script is to allow users to quickly create karaoke
for their karaoke.  It can be used in writing one script to generate
multiple karaoke.  The javascript prototypes enable users to apply
the karaoke to multiple compoisitions in one run.

@REQUIRE Karaoke_ssa.html: A converter from .ass to javascript object
(JSON) so that you can use it in this script.

To use this as a framework, have a variable defined before including
this script:

var krk = true ;
*************************************************************************/


/*
To import this API into your script, use:

var F = new File( "W:/_Work/ssa_karaoke.jsx" );
if ( F.exists )
{
	F.open( "r" , "TEXT" ) ;
	eval( F.read() )
}

// K is the Karaoke Data generated by Karaoke.html.

*/


//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////


/** KRKProject( K ) -- Main Karaoke Project Prototype: LEVEL 1
 * @param K -- Karaoke JSON, Generated from an external script
 */
function KRKProject( K )
{
	this.parentName = '' ;
	this.objectName = 'project' ;
	this.childName = 'comps' ;
	this.K = null ;
	this.caller = 'item' ;
	this.old = null ;
	this.unique = true ;
	this.expression = null ;
	this.project = app.project ;
	this.folder = null ;
	this.currentIndex = 0 ;
	this.krkComp ;
	this.krkText ;
	/**
	 * A Constructor
	 * @param K -- Karaoke JSON, Generated from an external script
	 */
	this.constructor = function( K )
	{
		this.old = this.project = app.project ;
		this.K = K ;
		if ( !K ) { return false ; }
		return this ;
	}
	
	/**
	 * prop( krk ) -- Resynchronize the same object [place holder]
	 * @param krk -- KRKProject object
	 */
	this.prop = function( krk )
	{
	}
	
/* Undos */
	
	/**
	 *  @function begin( undoText): undo text
	 *  @param undoText The text appearing in Edit->Undo
	 */
	this.begin = function( undoText )
	{
		if ( !undoText )
		{
			undoText = this.undo ? this.undo : "KRK Project" ;
		}
		app.beginUndoGroup( undoText ) ;
		this.removeAllComps( );
		this.removeAllLayers( ) ;
	}
	
	this.title = function( title )
	{
		this.undo = title ;
	}
	/**
	 * function end( ): end of undo
	 */
	this.end = function( )
	{
		app.endUndoGroup( ) ;
	}

	/**
	 * @function shiftKara( ): Shift everything in Karaoke JSON based on a time
	 * @param t time to be shifted
	 */
	this.shift = function( t )
	{
		if ( t )
		{
			var style , layer , line , syl , a , a0 , b , c ;
			for ( style in this.K )
			{
				a = this.K[style] ;
				for ( layer in a )
				{
					a0 = a[layer] ;
					for ( line = 0 ; line < a0.length ; line ++ )
					{
						b = a0[line] ;
						b.start += t ;
						b.end += t ;
						for ( syl = 0 ; syl < b.length ; syl ++ )
						{
							b[syl].time += t ;
						}
					}
				}
			}
		}
		return this ;
	}

	/**
	 * @function setFontSize( size , style , layerNumbers ) -- set the font size
	 * @param size -- font size in pixels
	 * @param styles -- selected styles defined in your .ass { StyleName: [layer numbers] }
	 */
	this.setFontSize = function( size , styles )
	{
		var i , j ;
		var ratio ;
		var style , s , s2 , s1 , layer ;
		var s2 = styles ? ( styles instanceof Array ? styles : [styles] ) : this.K ;
		for ( style in s2 )
		{
			k = this.K[styles ? s2[style] : style ] ;
			k.fontSize = size ;
			for ( i in k )
			{
				if ( i != Number( i ) ) { continue ; }
				s1 = k[i] ;
				for ( s = 0 ; s < s1.length ; s ++ )
				{
					k1 = s1[s] ;
					if ( ! k1.fontSize ) { k1.fontSize = 1 ; }
					ratio = size / k1.fontSize ;
					k1.fontSize = size ;
					k1.width *= ratio ;
					k1.height *= ratio ;
					for ( j = 0 ; j < k1.length ; j ++ )
					{
						k1[j].width *= ratio ;
						k1[j].height *= ratio ;
						k1[j].top *= ratio ;
						k1[j].left *= ratio ;
					}
				}
			}
		}
		return this ;
	}
	
	this.removeAllComps = function( )
	{
		var items = app.project.items ;
		var item ;
		var i;
		for ( i = items.length ; i > 0 ; i -- )
		{
			item = items[i] ;
			if ( item.name.match( /^__KRK/ ) )
			{
				item.remove( );
			}
		}
		this.folder = items.addFolder( '__KRK' ) ;
	}
	
	
	this.tempname = function( )
	{
		var name = "__KRK_" + this.currentIndex ; 
		this.currentIndex ++ ;
		return name ;
	}

	/**
	 * @function parseASS( x ) -- parses ASS Karaoke timings
	 * @param x -- content
	 * @return K.  Sets this.K to be the karaoke data.
	 */
	this.parseASS = function( x )
	{
		var StyleLayer ;
		var hh , text1 , text3;
		var K = { } ; var k ;
		var lines = x.split( /[\n\r]+/ ) ;
		var i , line , cells ;
		var j = { }  ;
		var Styles = { }; 
		var o = 0 ;
		var stylez, cells2, startTime, endTime, text, Kk ;
		hh;
		for ( i = 0 ; i < lines.length ; i ++ )
		{
			line = lines[ i ] ;
			cells = line.split( /,/ ) ;
			cells2 = cells[ 0 ].match( /\d+/ ) ;
			startTime = cells[ 1 ] ;
			endTime = cells[ 2 ] ;
			text = "" ;
			for ( ii = 9 ; ii < cells.length ; ii ++ )
			{
				text += ( ii > 9 ? "," : "" ) + cells[ ii ] ;
			}
			var notext = '' ;
			var K1, k1 ;
			if ( ( cells.length >= 10 ) && ( cells[ 0 ].match( /^Dialog/i ) ) )
			{
				var Style2 = cells[ 3 ].toLowerCase() ;
				var StyleLayer = cells2[0]
				var Style = Style2 + StyleLayer ;
				if ( !j[ Style ] )
				{
					if ( !K[Style2] )
					{
						K[Style2] = { } ;
					}
					j[Style] = 0 ;	
					Kk = K[Style2][StyleLayer] = { } ;
				}
				else
				{
					Kk = K[Style2][StyleLayer] ;
				}
				k = Kk[ j[Style] ] = { } ;
				k.style = Style ;
				k.start = this.secs( startTime ) ;
				k.end = this.secs( endTime ) ;
				if ( K1 = text.match( /\{\\k.*?-?\d+\}[^\{]*/gi ) )
				{
					kt = this.secs( startTime ) ;
					for ( k1 = 0 ; k1 < K1.length ; k1 ++ )
					{
						var KK = K1[ k1 ].match( /\{\\k.*?(-?\d+)\}(.*)/i ) ;
						text1 = KK[2] ; text3 = '' ;
						k[k1] =	{
							  text : text1
							, time : Math.round( kt * 1000 ) / 1000
							, dur : Math.round( KK[ 1 ] / 100 * 1000 ) / 1000
							} ;
						notext += text1
						kt += KK[ 1 ] / 100 ;
					}
					k.length = K1.length ;
				}
				else
				{
					notext = text.replace( /\{.+?\}/ , '' )
				}
				k.text = notext ;
				j[ Style ] ++ ;
			}
		}
	
		for ( var style in Styles )
		{
			for ( var layer in K[style] )
			{
				K[style][layer].length = j[style+layer] ;
			}
		}
		ii = 0 ;
		for ( i in K )
		{
			ii = 1 ;
			break ;
		}
		if ( !ii )
		{
			K = null ;
		}
		return this.K = K ;
	}

	/**
	 * @function secs(t)
	 * @param t -- .ass time fields
	 * @return seconds
	 */
 	this.secs = function( t )
	{
		var T = t.split( /:/ ) ;
		return( 3600.0 * Number( T[ 0 ] ) + 60.0 * Number( T[ 1 ] ) + 1.0 * Number( T[ 2 ] ) ) ;
	}

	/**
	 * commit all changes
	 * @TODO
	 */
	this.commit = function( undoName )
	{
		var i , j , k , layers , layer , newLayer , comps , comp ;
		for ( i in this.comps )
		{
			comp = this.comps[i] ;
			comp.commit( ) ;
		}
		if ( undoName != false )
		{
			this.end( ) ;
		}
	}

	this.topLayers = function( ) 
	{
		var o , i , j , k , l , comp , layer , genlayers ,layer2 ;
		for ( i in this.comps )
		{
			comp = this.comps[i] ;
			kk = k = 0 ;
			for( i in comp.layers )
			{
				if ( !k )
				{
					try{ ( layer = comp.layers[i].old ).moveToBeginning( ) } catch( err ){ k--; }
				}
				else
				{
					try{ ( comp.layers[i].old ).moveAfter( layer ) ;
					layer = comp.layers[i].old ; } catch( err ) { } 
				}
				k ++ ;
			}
			if ( comp.config )
			{
				comp.config.moveAfter( layer) ;
			}
		}
	}

	/**
	 * Auto Add Items
	 * Make sure your comps have been configured correctly
	 * @param codesOnly -- boolean: True if you want the codes quickly
	 */
	this.auto = function( codesOnly )
	{
		
	}

	this.codes = function( )
	{
		var o , layer , text , k , t , e ;
		if ( o = this.getAObject( "KRK" ) )
		{
			for ( i = 1 ; i <= o.numLayers ; i ++ ) 
			{
				layer = o.layer( i ) ;
				if ( ! ( text = layer('Text') ) )
				{
					continue ;
				}
				t = String( text('Source Text').value ) ;
				e = String( text('Source Text').expression ) ; 
				e = e ? e : String( layer.comment ) ;
				
				if ( this.parseASS( t ) )
				{
					this.expression = String( e ) ;
					return layer ;
				}
				else if ( this.parseASS( e ) )
				{
					this.expression = String( t ) ;
					return layer ;
				}
				else if ( t.match( /^[\s(]*\{.+\}[\s)]*$/ ) )
				{
					this.constructor( eval( '(' + t + ')' ) ) ;
					this.expression = String( e ) ;
					return layer ;
				}
				else if ( e.match( /^[\s(]*\{.+\}[\s)]*$/ ) )
				{
					this.constructor( eval( '(' + e + ')' ) ) ;
					this.expression = String( t ) ;
					return layer ;
				} 
			}
			return null ;
		}
	}


	this.@codes = function( )
	{
		var o , layer , text , k , t , e , comp ;
		if ( this.comps )
		{
			delete(this.comps);
		}
		if ( this.krkComp = o = this.getAObject( "KRK" ) )
		{
			for ( i = 1 ; i <= o.numLayers ; i ++ ) 
			{
				layer = o.layer( i ) ;
				if ( ( text = layer('Text') ) && text('Source Text').value )
				{
					this.krkText = layer ;
					this.parseASS( String( text('Source Text').value ) ) ;
					continue ;
				}
				if ( ! layer.shy && layer.source )
				{
					if ( layer.comment )
					{
						comp = this.add( layer.source ) ;
						if ( ! comp.configure( layer.comment , o ) ) { return false ; }
					}
				}
				
			}
			return comp ? true : undefined ;
		}
	}
	
	this.removeAllLayers = function( )
	{
		var o , i , j , k ;
		var l ;
		var comp , layer ;
		do
		{
			l = 0 ;			
			for ( i in this.comps )
			{
				comp = this.comps[i].comp ;
				for( i = 1 ; i <= comp.layers.length ; i ++ )
				{
					layer = comp.layers[i] ;
					if ( String(layer.name).match( /[^\[]+\[[^\]]+\]\[\d+\]/ ) )
					{
						l ++ ;
						// Throttled removals.
						while ( 1 )
						{
							try{ layer.locked = false ; layer.remove( ) }
							catch( err )
							{
								break ;
							}
						}
					}
				}
			}
		} while ( l ) ;
	} 

	this.evaluate = function( )
	{
		var comp , msg ;		
		if ( this.expression )
		{
			with( this )
			{
				try{ eval( this.expression.replace( /[��]/g , "'" ).replace( /[��]/g , '"' ) ) } // get rid of the stupid smart-quotes in ae expressions.
				catch( err )
				{
					alert( "You have an error in your codes! ( in KRK Composition )\n\n\"" + err.description + "\"\n\nClick OK to continue." ) ;
					return err.description ;
				}
			}
		}
	
		try
		{
			for ( comp in this.comps )
			{
				if ( msg = this.comps[comp].evaluate( ) )
				{
					alert( "You have an error in your codes in Composition:\n" +this.comps[comp].comp.name+"\n\n\"" + msg + "\"\n\nClick OK to continue." ) ;
					return msg ;
				}
			}
		}
		catch ( err )
		{
			alert( "You have not speciified any compositions [ i.e., add( \"Comp 1\"); ] and Karaoke JSON data in KRK comp. (in a Text Layer's Source Text and Source Text's expression) \n\n\nClick OK to continue." ) ;			
			return err.description ;
		}
		return null ;
	}

	this.destruct = function( )
	{
		var i ;
		for ( i in this.comps )
		{
			this.comps[i].destruct( ) ;
		}
		return this ;
	}


	


	this.constructor( K ) ;
}


//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////



/**
 * KRKComp( comp ) -- Main Karaoke Composition Prototype: LEVEL 2
 * @param comp -- After-Effects Comp object (can be numeric (index), string, or After-Effects Comp object)
 */
function KRKComp( comp )
{
/* Constructors */
	this.comp = comp ;
	this.old = null ;
	this.childName = 'layers' ;
	this.objectName = 'comp' ;
	this.caller = 'layer' ;
	this.project ;
	this.unique = true ;
	this.config = null ;
	this.xml = null ;
	this.markers = null ;
	/**
	 * constructor function
	 * @param comp -- After-Effects Comp object (can be numeric (index), string, or After-Effects Comp object)
	 */
	this.constructor = function( comp )
	{
		if ( comp != undefined )
		{
			if ( this.comp = comp instanceof Object ? comp : this.project.getAObject( comp ) )
			{
				this.old = this.comp ;
				this.name  = this.comp.name ;
				if ( this.config = this.comp.layer( "KRK" ) )
				{
					this.config.enabled = false ;
				}
			}
			else
			{
				throw "Composition does not exist: Make sure you typed in the name correctly!"
			}
		}
	}

	this.evaluate = function( )
	{
		var krk , text ,e ;
		if ( krk = this.comp.layer( "KRK" ) )
		{
			if ( text = krk( "Text" ) )
			{
				if ( !text.comment && ! text.sourceText ) { return false ; }
				with ( this )
				{
					e = ( text.sourceText ? String( text.sourceText.value ) : "" ) + "\n\n" + ( text.sourceText.expression ? String( text.sourceText.expression ) : "" ) + '\n\n' + ( krk.comment ? String(krk.comment) : "" ) ;
					try{ eval( e.replace( /[��]/g , "'" ).replace( /[��]/g , '"' ) ) } // get rid of the stupid smart-quotes in ae expressions.
					catch( err )
					{
						return ( err.description ) ;
					}
				}
			}
		}
		return null ;
	}

	this.getTimelineMarkers = function( )
	{
		var textLayer = this.comp.layers.addText('');
		var startTime = this.comp.startTime ;
		var i , marker , markers , text ;
// The only way to get composition markers is to use expressions.
		textLayer.Text.sourceText.expression = "var m=thisComp.marker;var x='';for(var i=1;i<=m.numKeys;i++){x+=(x==''?'':'&')+escape(m.key(i).time)+'='+escape(m.key(i).comment);}x" ;
		textLayer.Text.sourceText.expressionEnabled = true;
		text = String(textLayer.Text.sourceText.value) ;
		textLayer.remove( );
		markers = text.split('&');
		for ( i = 0 ; i < markers.length ; i ++ )
		{
			marker = markers[i].split('=',2);
			if ( marker.length < 2 )
			{
				continue ;
			}
			if ( !this.markers )
			{
				this.markers = [ ] ;
			}
			this.markers.push( { time: parseFloat( unescape( marker[0] ) ) , comment: unescape( marker[1] ) } ) ;
		}
	}

	this.configure = function( xml , krk )
	{
		var i = 0 ;
		var f ;
		var layer , children ;
		try
		{
			this.xml = new XML ('<root>'+xml+'</root>') ;
		}
		catch ( err )
		{
			xml__ = err.message.match( /\d+/ ) ;
			if ( xml__ )
			{
				xml__ = xml.split( "\n" , xml__[0] + 1 )[xml__[0] - 1].replace( /^\s+|\s+$/g , '' ) ;
			}
			else
			{
				xml__ 
			}
			
			this.errors( {
				message: 'Error parsing XML/Configuration.' ,
				xml: xml__
			} , err ) ;
		}
		this.getTimelineMarkers( ) ;
		// layers
		children = this.xml.elements( );
		for ( i = 0 ; i < children.length( ) ; i ++ )
		{
			if ( typeof ( this[f = '@' + (children[i].name().toString()).toLowerCase()] ) == 'function' )
			{
				this[f]( children[i] , krk ) ;
			}
		}
		return true ;
	}

	this.@copy = function( layer , krk )
	{
		var layer2 , layername ;
		var fromComp = this.xml_value( layer.@from ) ;
		var toComp = this.xml_value( layer.@to ) ;
		var fromLayer = this.xml_value( layer.@layer ) ;
		var toAlias = this.xml_value( layer.@name ) ;
		var overwrite = this.xml_bool( layer.@replace ) ;
		overwrite = typeof overwrite == 'undefined' ? 1 : overwrite ;
		try {
			fromComp = fromComp ? krk.layer( fromComp ).source : this.comp ;
			toComp = toComp ? krk.layer( toComp ).source : this.comp ;
			if ( fromComp != toComp && fromComp && toComp )
			{
				if ( fromComp )
				{
					layer = fromComp.layer( fromLayer ) ;
					layername = toAlias ? toAlias : layer.name ;
					if ( layer2 = toComp.layer( layername ) )
					{
						if ( overwrite )
						{
							while ( 1 )
							{
								try{ layer2.remove( ) }
								catch( err )
								{
									break ;
								}
							}
						}
					}
					else
					{
						overwrite = 1 ;
					}
				}
				if ( overwrite )
				{
					if ( toAlias )
					{
						layername = layer.name ;
						layer.name = toAlias ;
					}					
					layer.copyToComp( toComp ) ;
					if ( toAlias )
					{
						layer.name = layername ;
					}
				}
			}
		} catch( err )
		{
			this.error( {
				xml: this.xml.copy[i]
			} , err) ;
		}
	}

	this.@layer = function( layer , krk )
	{
		var disabled = this.xml_bool( layer.@disabled) ? 1 : undefined  ;
		var threed , krklayer , children , f , j , i ;
		var fixed ;
		try { 
			fixed = this.getFixed( layer.@fixed ) ;
			if ( typeof fixed == 'undefined' )
			{
				fixed = this.xml_bool(layer.@fixed);
			}
			else if ( typeof fixed == 'number' )
			{
				fixed = true ;
			}
			threed = this.xml_bool( layer.@threed)
			if ( typeof threed == 'undefined' ) { threed = this.xml_bool( layer.@threeD ) ; } 
			krklayer = this.add( this.xml_value( layer.@name  ) , 
			{
				space: this.xml_bool( layer.@space ) , 
				alias: this.xml_value( layer.@alias ) , 
				no: this.xml_value( layer.@no ) , 
				disabled: this.xml_bool(layer.@disabled) , 
				blending: this.xml_value( layer.@blend ) ,
				precomp: this.xml_bool( layer.@precomp ) ,
				threed: typeof threed == 'undefined' ? undefined : threed ,
				fixed: fixed
			} ) ; 
		} catch( err ) { 
			var description = 
				err.number == 21 ? "Layer's name is invalid." : undefined ;
			this.error( {
				message: 'Error adding a layer into comp.' ,
				comp: this.name ,
				layer: layer.@name ,
				xml: layer.toString().match( /^[^>]+/ )[0] + '>...' ,
				description: description
			} , err ) ; }
		children = layer.elements( );
		for ( j = 0 ; j < children.length( ) ; j ++ )
		{
			if ( typeof ( krklayer[f = '@' + (children[j].name().toString()).toLowerCase()] ) == 'function' )
			{
				try { krklayer[f]( children[j] ) ; }
				catch( err )
				{
					this.error(
						{
							message: err.message === '' || ! err.message || err.message == err.description ? "Error performing XML" : err.message ,
							comp: this.name ,
							layer: krklayer.name ,
							xml: children[i]
						} , err
					) ;
				}
			}
		}
	}

	/**
	 * prop( krk ) -- Resynchronize the same object
	 * @param krk -- KRKComp object
	 */
	this.prop = function( krk )
	{
		this.layers = krk.layers ;
		this.project = krk.project ;
	}

	this.commit = function( )
	{
		var layers = this.layers ;
		var newLayer ;
		var k = 0 ;
		var name ;
		for ( j in layers )
		{
			layer = layers[j] ;
			name = layer.old.name ;
			layer.old.moveToEnd( ) ;
			layer.old = this.comp.layer( name ) ;
			layer.commit( ) ;
		}
	}


	this.destruct = function( )
	{
		var i ;
		for ( i in this.layers )
		{
			this.layers[i].destruct( ) ;
		}
		return this ;
	}

	this.constructor(comp) ;
}


//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////




/**
 * KRKLayer( layer ) -- Main Karaoke Layer Prototype: LEVEL 3
 * @param layer -- After-Effects Layer object (can be numeric (index), string, or After-Effects Layer object)
 */
function KRKLayer( layer , options )
{
	this.options = { } ;
	this.parentName = 'comp' ;
	this.layer = null ;
	this.name = null ;
	this.parent = null ;
	this.time = { }  ;
	this.childName = 'properties' ;
	this.caller = 'property' ;
	this.objectName = 'layer' ;
	this.layers = null ;
	this.kara = null ;
	this.animators = { } ;
	this.properties = { } ;
	this.syl = null ;
	this.old = null ;
	this.unique = true ;
	this.created = false ;  // false for user input, true for automated
	this.commitAnimators;
	this.commitProperties;
	this.second = null ;
	this.blending = undefined ;
	this.setprop = [ ] ;
	this.precomp = false ;
	this.threed = undefined ;
	this.sizes = null ;
	
	/**
	 * A function to resynchronize the properties on this object
	 * @param property -- KRKProperty object
	 */
	this.pr = function( property )
	{
		var krk , i , Krk ;
		Krk = [ ] ;
		if ( ! ( property instanceof Array ) )
		{
			property = [ property ] ;
		}
		for ( i in property )
		{
			if ( property[i] instanceof Object )
			{
				krk = property[i] ;
			}
			else
			{
				krk = this.properties[property[i]] ;
			}	
			krk.layer = this ;
			krk.property = this.getProperty( this.layer , krk.names ) ;
			Krk.push(krk);
		}
		return Krk.length > 1 ? Krk : Krk[0] ;
	}
	
	/**
	 * A function to resynchronize the animators in this object
	 * @param animator -- KRKAnimator object
	 */
	this.an = function( animator )
	{
		var krk , i , Krk ;
		Krk = [ ] ;
		if ( ! ( animator instanceof Array ) )
		{
			animator = [ animator ] ;
		}
		for ( i in animator )
		{
			if ( animator[i] instanceof Object )
			{
				krk = animator[i] ;
			}
			else
			{
				krk = this.animators[animator[i]] ;
			}	
			krk.layer = this ;
			if (krk.end instanceof Object)
			{
				krk.end = this.getProperty( this.layer , krk.animator.end.names ) ;
				krk.end.layer = this ;
			}
			if (krk.start instanceof Object)
			{
				krk.start = this.getProperty( this.layer , krk.animator.start.names ) ;
				krk.start.layer = this ;
			}
			if (krk.offset instanceof Object)
			{
				krk.offset = this.getProperty( this.layer , krk.animator.offset.names ) ;
				krk.offset.layer = this ;	
			}
			Krk.push(krk);
		}
		return Krk.length > 1 ? Krk : Krk[0] ;
	}
	
	/**
	 * uses a layer from the pool
	 * @param key -- the key in the pool
	 */
	this.uses = function( key )
	{
		this.layer = key instanceof Object ? key : this.layers[key] ;
	}

	this.destruct = function( ) 
	{
		if ( this.old.Text) 
		{
			var o = this.old("Text")("Animators")( "KRK Syllable" ) ;
			while( o )
			{
				try { o.remove( ) ; } catch( err ) { break ; }
			}
		}
		return this ;
	}

	/**
	 * constructor function
	 * @param layer -- After-Effects Layer object (can be numeric (index), string, or After-Effects Layer object)
	 * @param time -- time settings (default reads from the Markers in your comp)
	 * @see KRKLayer::readPresetTimes
	 */
	this.constructor = function( layer , options )
	{
		var i;
		if ( layer == undefined ) { return false ; } 
		this.layer = this.comp.getAObject( layer ) ;		
		for ( i = 0 ; this.comp.layers[ this.name = options.alias ? options.alias : ( i ? this.layer.name + "_" + String( i ) : this.layer.name ) ] ; i ++ ) ;
		if ( typeof options == 'object' )
		{
			this.options = options ;
		}
		if ( this.options.disabled )
		{
			this.disabled = true ;
			this.readPresetTimes( options.fixed ) ;
		}
		if ( this.options.time )
		{
			this.time = this.options.time ;
		}
		else
		{
			this.readPresetTimes( options.fixed ) ;
		}

		var blend ;
		if ( blend = options.blending )
		{
			blend = blend.toUpperCase().replace( /\s+/g , '_' ) ;
			if ( typeof BlendingMode[blend] != 'undefined' )
			{
				this.blending = blend;
			}
 		}
		if ( options.precomp )
		{
			this.precomp = true ;
		}
		this.threed = this.options.threed ;
		this.disabled = this.options.disabled ;
		this.addlayers = [ ] ;
		this.layers = { } ;
		this.layers2 = { } ;
		this.animators = { } ;
		this.commitProperties = { } ;
		this.commitAnimators = { } ;
		this.kara = null ;
		this.old = this.layer ;
//		if ( ! this.name.match( /_/ ) )
//		{
			// Adding a syllable text animator for positionings
			if ( ! this.options.no )
			{
				this.createSyllableAnimator( ) ;
			}
//		}
		return this ;
	}

	/**
	 * adda -- Add Animator(s)
	 * @param object
	 * @see KRKCommon::add
	 * @return the KRKAnimator objects
	 */
	this.adda = function( animators , options )
	{
		return this.layer('Text') ? this.add( animators , options , 'animators' ) : null ;
	}

	this.addp = function( properties , options )
	{
		return this.add( properties , options , 'properties' ) ;
	}

	this.p = function( properties , options )
	{
		var prop = [ ] ;
		var i ;
		this.recursivePropertyKeysSearch( properties , prop ) ;
		
		for ( i in prop )
		{
			this.addp( prop[i] , options );
		}
		return this ;
	}
	
	this.@p = this.@prop = this.@property = function( x )
	{
		var fixed = this.xml_bool( x.@fixed ) ;
		var fixed2 = this.xml_value( x.@fixed ) ;
		if ( fixed )
		{
			switch( fixed2 )
			{
				case "start":
			}
		}
		var xx = { syl: this.xml_bool( x.@syl ) ? true : undefined , fixed: this.getFixed( x.@fixed ) , pos: this.xml_value( x.@pos ) } ;
		var comp = this.xml_value( x.@comp ) ;
		var layer = this.xml_value( x.@layer ) ;
		var link = this.xml_value( x.@link ) ;
		if ( link )
		{
			if ( comp && layer )
			{
				xx.link = [ comp , layer , link ] ;
			}
			else if ( layer )
			{
				xx.link = [ this.comp.comp.name , layer , link ] ;
			}
		}
		this.p( x.@name.toString() , xx ) ;
	}

	this.@dim = this.@dimension = function( x )
	{
		var size = this.xml_value( x.@size ) ;
		var loc = this.xml_value( x.@position ) ;
		var prop ;
		var bad = undefined ;
		var extents = this.xml_bool( x.@extents ) ;
		if ( typeof extents == 'undefined' ) { extents = true ; }
		if ( typeof loc == 'undefined' )
		{
			loc = this.xml_value( x.@pos ) ;
		}
		var duration = this.xml_value( x.@dur ) ;
		if ( typeof duration == 'undefined' )
		{
			duration = this.xml_value( x.@duration ) ;
		}
		if ( loc || size )
		{
			// Check
			if ( loc )
			{
				if ( !this.getProperty(this.layer,loc) )
				{
					throw { message: "Bad Property: " + loc , layer: this.name , comp: this.comp.name , xml: x } ;
				}
			}
			if ( size)
			{
				if ( !this.getProperty(this.layer,size) )
				{
					throw { message: "Bad Property: " + size , layer: this.name , comp: this.comp.name , xml: x } ;
				}
			}
			this.sizes = { pos: loc , size: size , dur: duration , extents: extents } ;
		}
		else
		{
			throw { message: "You need to specify location or size, and or duration attributes in <dim>" , layer: this.name , comp: this.comp.name , xml:x } ;
		}
	}

	this.a = function( animators , options )
	{
		this.adda( animators , options ) ;
		return this ;
	}

	this.@a = this.@anim = this.@animate = this.@animator = function( x )
	{
		try
		{
			this.a( x.@name.toString() , { syl: this.xml_bool( x.@syl ) ? true : undefined, fixed: this.getFixed( x.@fixed ) } ) ;
		}
		catch(err)
		{
			this.error( {message: err.number == 21 ? "Bad Animator's name.  Make sure you have your animator's name correct!" : err.message } , err ) ;
		}
	}

	/**
	 * prop( krk ) -- Resynchronize the same object
	 * @param krk -- KRKLayer object
	 */
	this.prop = function( krk )
	{
		this.properties = krk.properties ;
		this.animators = krk.animators ;
		this.comp = krk.comp ;
	}
	
	/**
	 * disables all the text animators
	 * @param layer -- layer (optional -- this.layer is the default)
	 * @return this if success, false if not.
	 */
	this.disableAllTextAnimators = function( layer )
	{
		if ( !layer )
		{
			layer = this.layer ;
		}
		var i = 1 ;
		var o ;
		if ( ! layer.Text ) { return false ; }
		var p = layer("Text")("Animators");
		try
		{
			while( o = p(i++) )
			{
				o.enabled = false ;
			}
		}
		catch( err ) { }
		return this ;
	}

	/**
	 * Set after-effects property
	 * @param propertyName -- property name of the generated layer
	 * @param property -- AFX property name
	 * @param value -- value you want to set with
	 * @example s( 'Effects.Glow' , 'enabled' , false ) ;
	 */
	this.s = function( propertyName , property , value )
	{
		this.setprop.push( { name : propertyName , property : property , value : value } ) ;
		return this ;
	}

	this.@s = this.@set = this.@setting = function( x )
	{
		var layer = this.xml_value( x.@layer ) ;
		if ( layer )
		{
			this.s( this.xml_value( x.@name ) , 'layer' , layer ) ;
		}
		else
		{
			this.s( this.xml_value( x.@name ) , this.xml_value( x.@property ) , this.xml_value( x.@value ) ) ;
		}
		return this ;
	}

	this.createSyllableAnimator = function( layer )
	{
		if ( !layer )
		{
			layer = this.old ;
		}	
		if ( !layer.Text )
		{
			return false ;
		}
		while( layer("Text")("Animators")("KRK Syllable" ) )
		{
			try { layer("Text")("Animators")("KRK Syllable").remove( ) } catch( err ) { break ; }
		}
		var anim = this.layer("Text")("Animators").addProperty( "Text Animator" ) ;
		anim.name = "KRK Syllable" ;
		var scale = anim("ADBE Text Animator Properties").addProperty("ADBE Text Scale 3D") ;
		var newsel = anim("Selectors").addProperty("ADBE Text Selector");
		var avg = ( this.time.endTime + this.time.startTime ) / 2 
		newsel.property("Start").setValueAtTime( this.time.startTime , 0 ) ;
		newsel.property("Start").setValueAtTime( avg , 100 ) ;		
		newsel.property("End").setValueAtTime( this.time.endTime , 100 ) ;
		newsel.property( "End" ).setValueAtTime(  avg , 0 ) ;
		newsel.advanced.mode.setValue( 2 ) ;
		newsel.advanced.units.setValue( 1 ) ;		
		try { scale.setValue( [0,0] ) ; } catch( err ) { scale.setValue( [0,0,0] ) ; }
		this.a( "KRK Syllable" , true ) ;
	}

	this.getDim = function( layerName )
	{
		var i ;
		var rect ;
		var K = this.getKaraObject( ) ;
		var layer ;
		if ( ! ( layer = this.layers[layerName] ) )
		{
			layer = this.comp.comp.layer( layerName ) ;
		}
		var o = layerName instanceof Object ? layerName : this.parseLayerName( layerName , K ) ;

		var syllable = layer("Text")("Animators")("KRK Syllable") ;
		syllable.enabled = true ;
		if ( o.syl )
		{
			rect[o.names.line] = layer.sourceRectAtTime( o.syl.time + o.syl.dur/2 , true ) ;
			syllable.enabled = false ;
		}
		else if ( o.line )
		{
			var j , k , height, width ;
			k = -1 ;
			rect = { } ;
			for ( i = 0 ; i < o.line.length ; i ++ )
			{
				if ( o.line[i].text.match( /^\s*$/ ) )
				{
					for ( j = i + 1 ; j < o.line.length ; j ++ )
					{
						if ( o.line[j].text.match( /^\s*$/ ) )
						{
							continue ;
						}
						else
						{
							break ;
						}
					}
					if ( j < o.line.length )
					{
						rect[i] = layer.sourceRectAtTime( o.line[j].time + o.line[j].dur/2 , true ) ;
						if ( k >= 0 )
						{
							rect[i].top = ( rect[i].top + rect[k].top ) / ( j - k ) ;
							rect[i].left = ( rect[i].left + rect[k].left ) / ( j - k ) ;
							width = ( rect[i].width - rect[k].width ) ;
							height = ( rect[i].height - rect[k].height ) ;
							rect[i].width = ( width > 3 ? ( rect[i].left - rect[k].left ) : width ) / ( j - k - 1 ) ;
							rect[i].height = ( height > 3 ? ( rect[i].top - rect[k].top ) : height ) / ( j - k - 1 ) ;
						}
					}
					else
					{
						if ( k >= 0 )
						{
							rect[i] = rect[k] ;
						}
						else
						{
							rect[i] = null ;
						}
					}
					continue ;
				}
				else
				{
					k = i ;
				}
				rect[i] = layer.sourceRectAtTime( o.line[i].time + o.line[i].dur / 2 , true ) ;
			}
		}
		syllable.enabled = false ;
		return rect ;
	}

	this.getDims = function () 
	{
		var K = this.getKaraObject( ) ;
		var k;
		var i , j , jj  , markers ;
		var size , dim , layer , bounds ;
		var size0 , dim0 , value ;
		if ( !this.sizes ) { return ; }
		var dur = parseFloat( this.sizes.dur ) ;
		var t , t1 ;
		var extents = this.sizes.extents ;
		if ( !this.sizes ) { return ; }
		for ( i in this.layers )
		{
			layer = this.layers[i] ;
			t = layer.inPoint ;
			t1 = layer.outPoint ;
			if ( this.sizes.size && ( size = this.getProperty( layer , this.sizes.size ) ) )
			{
				while( size.numKeys )
				{
					size.removeKey( 1 ) ; // remove keyframes.
				}
				size0 = size.value ;
			}
			
			if ( this.sizes.pos && ( dim = this.getProperty( layer , this.sizes.pos ) ) )
			{
				while( dim.numKeys )
				{
					dim.removeKey( 1 ) ; // remove keyframes.
				}
				dim0 = dim.value ;
			}
			if ( dur > 0 )
			{
				while( t <= t1 )
				{
					bounds = layer.sourceRectAtTime( t , extents ) ;
					if ( size )
					{
						size.setValueAtTime( t , this.mergeArray( size0 , [ bounds.width , bounds.height ]  ) ) ;
					}
					if ( dim )
					{
						dim.setValueAtTime( t , this.mergeArray( dim0 , [ bounds.top , bounds.left ]  ) ) ;
					}
					t += dur ;
				}
			}
			else if ( ! this.sizes.dur )
			{
				bounds = layer.sourceRectAtTime( t , extents );
				if ( size )
				{
					size.setValue( this.mergeArray( size0 , [ bounds.width , bounds.height ]  ) ) ;
				}
				if ( dim )
				{
					dim.setValue( this.mergeArray( dim0 , [ bounds.top , bounds.left ]  ) ) ;
				}
			}
			else
			{ // Get from syllable timings
				k = this.parseLayerName( layer , K ) ;
				k = k.syl ? [ k.syl ] : ( k.line ? k.line : null ) ;
				if ( k )
				{
					for ( j = 0 ; j < k.length ; j ++ )
					{
						for ( jj = 0 ; jj < 2 ; jj ++ )
						{
							t = jj ? k[j].time + k[j].dur : k[j].time ;
							bounds = layer.sourceRectAtTime( t , extents ) ;
							if ( size )
							{
								size.setValueAtTime( t , this.mergeArray( size0 , [ bounds.width , bounds.height ]  ) ) ;
							}
							if ( dim )
							{
								dim.setValueAtTime( t , this.mergeArray( dim0 , [ bounds.top , bounds.left ]  ) ) ;
							}
						}
					}
				}
			}
		}
	}
	
	this.mergeArray = function( array1 , array2 )
	{
		var i ;
		for ( i = 0 ; i < array1.length ; i ++)
		{
			if ( i < array2.length )
			{
				array1[i] = array2[i] ;
			}
			else
			{
				break ;
			}
		}
		return array1 ;
	}
	
	this.commit = function( )
	{
		var i , j , l , m ;
		var name = { } ;
		var object = { } ;
		var layerName ;
		var layer ;
		var property ;
		var newLayer , k ;
		this.old.selected = this.old.enabled = false ;
		var tmp  ;
		var item , item0 , $name ;
		var threed , index ;
		var $project = this.getParent("project")  ;
		var location , size ;
		// Check for default properties:
		try
		{
			if ( this.old("Text") )
			{
				// Check for autospaces:
				this.spacing = this.getAutoSpacing( ) ;
				try { this.p( "effect('Position')('Point')" , {pos: this.old.name, syl:true} ) ; } catch( err ) { }
			}
		}
		catch( err ){ this.error( {
					message: "Error calculating zero space" ,
					layer: this.name ,
					comp: this.comp.name
				} , err ) ; }

		// Adding new layers
		for ( k = 0 ; k < this.addlayers.length ; k ++ )
		{
			this.addl( this.addlayers[k]['key'] , this.addlayers[k]['options'] ) ;
		}		
		
		// Exclude any names with _.
		if ( this.old("Text") ) // && ! this.name.match( /_/ ) )
		{
			// Creating the syllable animators to detect the sizes .
			for ( k in this.layers )
			{
				this.layer = this.layers[k] ;
				if ( this.animators['KRK Syllable'] )
				{
					this.animators['KRK Syllable'].commit( ) ;
					this.layer("Text")("Animators")("KRK Syllable").enabled = false ;
				}
			}
		}
		
		// delete this object so that it won't be generated ever again.		
		delete this.animators['KRK Syllable'] ;
		var layerName , layerStuff , e , pr , c , c2 ;

		
		// commit properties and animators
		for ( i in this.layers )
		{
			layer = this.layers[i];
			if ( !layer ) { continue ; }
			layerName = this.parseLayerName( layer.name ) ;
			this.layer = layer ;
			if ( typeof this.threed != 'undefined' )
			{
				try{ layer.threeDLayer = this.threed } catch(e){ }
			}
			tmp = this.commitProperties[layerName] ;
			for ( j in tmp ? tmp : this.properties )
			{
				property = tmp ? this.properties[tmp[j]] : this.properties[j] ;
				property.property = this.getProperty( this.layer , property.names ) ;
				property.commit( ) ;
			}
			tmp = this.commitAnimators[layerName];
			for ( j in tmp ? tmp : this.animators )
			{
				this.animators[tmp ? tmp[j] : j].commit( ) ;
			}
		}
	
	
		// Setting them as enabled, and set proper properties
		for ( k in this.layers )
		{
			newLayer = this.layers[k] ;
			if ( newLayer != this.old )
			{
				newLayer.selected = newLayer.enabled = ! this.disabled ;
				layerName = this.parseLayerName( newLayer.name ) ;
			// setting properties
				for ( l in this.setprop )
				{
					if ( this.setprop[l].property == 'layer' )
					{
						try {						
						layerStuff = this.layerNaming( 
						  this.setprop[l].value
						, layerName.style
						, layerName.layer
						, layerName.line
						, layerName.syl ) ;
						layerStuff2 = this.layerNaming( 
						  this.setprop[l].value
						, layerName.style
						, layerName.layer
						, layerName.line ) ;
						c = this.comp.comp ;
						property = this.getProperty( newLayer , this.setprop[l].name ) ;
						if ( pr = c.layer( layerStuff ) )
						{
							property.setValue( pr.index ) ;
						}
						else if ( pr = c.layer( layerStuff2 ) )
						{
							property.setValue( pr.index ) ;
						}
						} catch( err ) { throw( { number: err.number , layer: this.layer.name , description: "Error setting property: "+this.setprop[l].name+"\nTo layer: " + this.setprop[l].value  } ) ; }
					}
					else if ( typeof this.setprop[l].value != 'undefined' )
					{
						try {
						property = this.getProperty( newLayer , this.setprop[l].name ) ;
						property[ this.setprop[l].property ] = this.setprop[l].value ; } catch(err) { this.error( { layer: this.layer.name , description: "Error setting property: "+this.setprop[l].name+"  ("+this.setprop[l].property+")\nWith value: " + this.setprop[l].value  } , err ) ; }
					}
					else
					{
						try{
						newLayer[this.setprop[l].name] = this.setprop[l].property ; } catch(err){ this.error( { layer: this.layer.name , description: "Error setting property: "+this.setprop[l].name+"  ("+this.setprop[l].property + ')'  } , err ) ; }
					}
				}
			
			// running sizes
				this.getDims( ) ;

			// precomposing
				if ( this.precomp )
				{
					var markers ;
					threed  = newLayer.threeDLayer ;
					$name = newLayer.name ;
					item = this.comp.comp.layers.precompose( [ index = newLayer.index ] , $name , true ) ;
					item.parentFolder = $project.folder ;
					item0 = this.comp.comp.layers[index];
					newLayer = item0.source.layer(1) ;
					markers = newLayer( "Marker" ) ;					
					if ( markers )
					{
						for ( i = 1 ; i <= markers.numKeys ; i ++ )
						{
							item0("Marker").setValueAtTime( markers.keyTime( i ) , markers.keyValue( i ) ) ;
						}
					}
					if ( item0.canSetCollapseTransformation )
					{
						item0.collapseTransformation = true ;
					}
					item0.threeDLayer = threed ;
					item0.name = $name ;
					i = newLayer.stretch / 100 ;
					item0.inPoint = newLayer.inPoint * i + newLayer.startTime ;
					item0.outPoint = newLayer.outPoint * i + newLayer.startTime ;
				}
			}
		}
	}



	this.timeFunction = this.defaultTimeFunction ;
	this.valueFunction = this.defaultValueFunction ;


	/**
	 * setText -- set your text in this.layer
	 * @param kline -- Karaoke line
	 * @param spacing -- If there's a zero-word spacing
	 *                   Default searches for "Spacing" expression control
	 *                   Can be the name of the expression control or a number for your spacing
	 */
	this.setText = function( kline )
	{
		var anim , tracking , lineLen , m , newsel ;
		var layer ;
		var name ;
		var textLayer ;
		var m , n ;
		var K = this.parseLayerName( this.layer.name , this.getKaraObject( ) ) ;
		// If it's not a text, return false.
		if ( ! ( textLayer = this.layer('Text') ) )
		{
			return false ;
		}		
		// Zero-Word Spacing Control
		if ( this.spacing )
		{
			// Setting Zero-Word Spacing for per-syllable karaoke
			anim = this.layer("Text")("Animators").addProperty( "Text Animator" ) ;
			anim.name = "Zero Spacing" ;
			// Adding Tracking for the spacing
			tracking = anim("ADBE Text Animator Properties").addProperty("ADBE Text Tracking Amount") ;
			tracking.setValue( this.spacing ) ;
			if ( kline instanceof Array )
			{
				lineLen = kline[0].text.length + kline[0].length + kline[1].text.length + kline[1].length;
				text = '' ;
				var len = 0 ;
				for ( n = 0 ; n < 2 ; n ++ )
				{
					for ( m = 0 ; m < kline[n].length ; m ++ )
					{
						text += kline[n][m].text + ' ' ;
						len += kline[n][m].text.length + 1 ;
						newsel = anim("Selectors").addProperty("ADBE Text Selector");
						newsel.property("Start").setValue( ( len - 1 ) / lineLen * 100 ) ;
						newsel.property("End").setValue( ( len ) / lineLen * 100 ) ;
						newsel.name = "Spacing " + n + ',' + m; // Spacing 0,0, Spacing 1,0 ...
					}
					if ( !n ) { text += "\r\n" ; }
				}
				this.layer("sourceText").setValue( text ) ;
			}
			else
			{
				lineLen = kline.text.length + kline.length;
				for ( text = '' , m = 0 ; m < kline.length ; m ++ )
				{
					text += kline[m].text + ' ' ;
					newsel = anim("Selectors").addProperty("ADBE Text Selector");
					newsel.property("Start").setValue( ( text.length - 1 ) / lineLen * 100 ) ;
					newsel.property("End").setValue( ( text.length ) / lineLen * 100 ) ;
					newsel.name = "Spacing " + m; // Spacing 0, Spacing 1 ...
				}
				this.layer("sourceText").setValue( text ) ;
			}
		}
		else
		{
			this.layer("sourceText").setValue( kline instanceof Array ? kline[0].text + "\r\n" + kline[1].text : kline.text ) ;
		}
		return this ;
	}
	/**
	 * getAutoSpacing
	 * =============
	 * Gets autospacing from layer
	 */
	this.getAutoSpacing = function( )
	{
		if ( this.layer.text.moreOption( "Anchor Point Grouping" ).value != 2 && ! this.options.space ) { return false ; }
		var add = -1 ;
		var t1 = 0 ;
		var text ;
		var sourceRect ;
		var s ;
		var t ;
		var anim ;
		var tracking ;
		var newsel ;
		var cur = 0 ;
		var p ;
		var sourceRectWidth = null ;
		var isWidth = null ;
		if ( this.old.property( 'Text' ) )
		{
			var layer = this.old.duplicate( ) ;
			layer.name = "Temp Spacing[Word][0][0]" ;
			this.disableAllTextAnimators( layer ) ;
			text = layer.property("Text");
			text.sourceText.setValue( "KRKKRK" ) ;
			sourceRect = layer.sourceRectAtTime( 0 , true ) ;
			text.sourceText.setValue( t = "KRK KRK" ) ;
			anim = text("Animators").addProperty( "Text Animator" ) ;
			anim.name = "Zero Spacing" ;
			anim.enabled = true ;
			// Adding Tracking for the spacing
			tracking = anim("ADBE Text Animator Properties").addProperty("ADBE Text Tracking Amount") ;
			newsel = anim("Selectors").addProperty("ADBE Text Selector");
			newsel.start.setValue( 300 / t.length ) ;
			newsel.end.setValue( 100 * ( t.length - 3.0 ) / t.length ) ;
			add = -0.5 ;
			do
			{
				add *= 2 ;
				tracking.setValue( t1 += add );
				s = layer.sourceRectAtTime( 0 , true ) ;
				if ( ! sourceRectWidth )
				{
					sourceRectWidth = Math.abs( sourceRect.width - s.width ) > 1 ? (isWidth = sourceRect.width) : sourceRect.height ;
				}
			} while ( ( isWidth ? s.width : s.height ) > sourceRectWidth ) ;

			do
			{
				add /= 2 ;
				tracking.setValue( t1 += ( isWidth ? s.width : s.height ) < sourceRectWidth ? -add : +add ) ;
				s = layer.sourceRectAtTime( 0 , true ) ;				
			}
			while( Math.abs( add ) > 0.00025 ) ;
		}
		t1 = Math.round( t1 * 1000 ) / 1000 ;
		do
		{
			try{ layer.remove( ) } catch( err ) { break ; }
		} while( layer ) ;
		return t1 ;
	}

	this.resetMarkers = function( startTime , endTime )
	{
		var marker = this.layer("Marker") ;
		var i ;
		var n = marker.numKeys ;
		var startMarker = null , endMarker = null ;
		var K = this.getKaraObject( ) ;
		var names = this.parseLayerName( marker.parentProperty.name ) ;
		var text = names.syl ? K[names.style][names.layer][names.line][names.syl].text : K[names.style][names.layer][names.line].text ;
		for ( i = 1 ; i <= n ; i ++ )
		{
			if ( i == this.time.startMarker )
			{
				startMarker = marker.keyValue( 1 ) ;
			}
			else if ( i == this.time.endMarker )
			{
				endMarker = marker.keyValue( 1 ) ;
			}
			marker.removeKey( 1 ) ;
		}
		marker.setValueAtTime( startTime , new MarkerValue( text ) ) ;
		marker.setValueAtTime( endTime , new MarkerValue("") ) ;
	}
	/**
	 * create a new layer based on the current template layer
	 * @param spacing -- zero-spacing configuration (default = 'Spacing' expression control, can be a number or the name of the control)
	 * @param style -- Style in Karaoke JSON
	 * @param layerNumber -- Layer number in Karaoke JSON
	 * @param lineNumber -- Line number in Karaoke JSON
	 * @param syllableNumber -- Syllable number in Karaoke JSON (default is off--generate the whole line or else--generate one line)
	 */
	this.create = function( spacing , style , layerNumber , lineNumber , syllableNumber , fixed )
	{
		var K = this.getKaraObject( ) ;
		var k ;
		var name , kara , o ;
		var startTime , endTime ;
		var text = style instanceof Object ? [ K[style[0]][layerNumber][lineNumber] , K[style[1]][layerNumber][lineNumber] ] : K[style][layerNumber][lineNumber] ;
		if ( typeof fixed == 'undefined' )
		{
			fixed = this.time.fixed ;
		}
		if ( style != undefined && lineNumber != undefined )
		{
			var sylNum = syllableNumber && Number( syllableNumber ) < 0 ;
			if ( syllableNumber != undefined && syllableNumber != null && !sylNum )
			{
				name = this.layerNaming( this.name , style instanceof Object ? style[0] : style , layerNumber , lineNumber , syllableNumber ) ;
				k = K[ style instanceof Object ? style[0] : style ][layerNumber][lineNumber][syllableNumber] ;
				o = { startTime: k.time , endTime: k.time + k.dur } ;
				o.fixed = fixed === true || fixed === '^' ? '^' : false ;
				startTime = this.originalTimeFunction( this.time.inTime == undefined ? this.old.inPoint : this.time.inTime , 0 , o ) ;
				o.fixed = fixed === true || fixed === '$' ? '$' : false ;
				endTime = this.originalTimeFunction( this.time.outTime == undefined  ? this.old.outPoint : this.time.outTime , 0 , o ) ;
				kara = [  style instanceof Object ? style[0] : style  , layerNumber , lineNumber , syllableNumber ] ;
			}
			else
			{
				name = this.layerNaming( this.name ,  style instanceof Object ? style[0] : style  , layerNumber , lineNumber , sylNum ? -Number( syllableNumber ) : undefined ) ;
				k = K[ style instanceof Object ? style[0] : style ][layerNumber][lineNumber] ;
				o = { startTime: k.start , endTime: k.end } ;
				o.fixed = fixed === true || fixed === '^' ? '^' : false ;
				startTime = this.originalTimeFunction( this.time.inTime == undefined ? this.old.inPoint : this.time.inTime , 0 , o ) ;
				o.fixed = fixed === true || fixed === '$' ? '$' : false ;
				endTime = this.originalTimeFunction( this.time.outTime == undefined  ? this.old.outPoint : this.time.outTime , 0 , o ) ;
				kara = [  style instanceof Object ? style[0] : style  , layerNumber , lineNumber ] ;				
			}
		}
		if ( o )
		{
			var layer = this.old.duplicate( ) ;
			this.layer = layer ;
			this.disableAllTextAnimators( ) ;
//			name += String( Math.floor( Math.random() * 9000 ) + 1000 ) ;
			layer.name = name ;
			layer.inPoint = startTime ;
			layer.outPoint = endTime ;
			if ( layer("Text") )
			{
				if ( spacing )
				{
					this.setText( text , spacing ) ;
				}
				else
				{
					this.setText( text ) ;
				}
			}
			this.resetMarkers( o.startTime , o.endTime ) ;
			this.layers[layer.name] = layer;
			this.syl = syllableNumber != undefined || syllableNumber != null ? null : syllableNumber ;
			if ( typeof this.blending != 'undefined' )
			{
				layer.blendingMode = BlendingMode[this.blending] ;
			}
			
			return layer ;
		}
		return null ;		
	}

	/**
	 * addl -- adds layers from the current template
	 * @param name -- name or object of the After-Effects Layer object
	 * @param key -- Keys for the Karaoke JSON: Style->Layer number->Line number->Syllable number.
	 *               null for everything
	 *               ['romaji'], { romaji: null } for only romaji
	 *               { romaji: [0,1] } for only romaji and Layer #0 and #1
	 *               { romaji: {0: [0,1,2,3:[3,4,5]]} }, etc
	 * @param is_syl -- If only syllable layers are created (default is false)
	 * @param spacing -- If spacing is undefined, use default zero-word spacing
	 *                   'Spacing' expression control must be present in your layer template for the spacing
	 *                   spacing can be the expression control name or a number.
	 * @param anim -- list of animators that can be applied there through commit function
	 * @param prop -- list of properties that can be applied there through commit function
	 * @param fixed -- tells the script to add fixedalized lead-in and lead-out to the line/syllable.
	 * @note All the generated layers are stored in layers[].layers[]
	 */
	this.addl = function( keys , options )
	{
		var K = this.getKaraObject( ) ;
		var anim = options.anim == undefined ? null : ( options.anim instanceof Array ? options.anim : [options.anim] ) ;
		var prop = options.prop == undefined ? null : ( options.prop instanceof Array ? options.prop : [options.prop] ) ;
		var name ;
		var level = options.syl ? this.KRK_SYLLABLE : this.KRK_LINE ;
		for ( i in keys )
		{
			k = keys[i] ;
			if ( k.length == level )
			{
				try {
					this.layer = this.create( options.spacing == undefined ? null : options.spacing , options[2] ? [ k[0] , options[2] ] : k[0] , k[1] , k[2] , options.syl ? ( options.syl == "all" ? -k[3] : k[3] ) : undefined , options.fixed ) ;
					if ( options[2] )
					{
						this.layers2[this.layer.name] = options[2] ;
					}
					if ( anim ) { this.commitAnimators[this.layer.name] ; }
					if ( prop ) { this.commitProperties[this.layer.name] ; }
				}
				catch( err )
				{
					this.error( {
							message: "Error adding lines" ,
							layer: this.name ,
							comp: this.comp.name ,
							kara: { style: k[0] , layer: k[1], line: k[2] + 1 , syl: k[3] + 1 }
						} , 
					err ) ; 
				}
			}
		}
		return this ;
	}
	
	this.l = function( key , options )
	{
		var K = this.getKaraObject( ) ;
		var style, layer, line, syl ;
		var k0, k1, k2 ;
		var template ;

		var level ;
		var l ;
		if ( !options )
		{
			options = { } ;
		}
		if ( !( options instanceof Object ) )
		{
			options = {syl: options} ;
		}
		level = options.syl ? this.KRK_SYLLABLE : this.KRK_LINE ;
		template = this.layers.old ;
		// var keys = this.recurseObject( K , level , key == undefined ? null : key ) ;
//		var keys = this.recurseKaraoke( level  , key ) ;
//		this.addl( key , options ) ;
		var keys = [ ] ;
		for ( style in K )
		{
			if ( typeof key.style == 'undefined' || ( typeof key.style == 'object' && key.style[style] ) )
			{
				k0 = K[style] ; for ( layer in k0 )
				{
					if ( typeof key.layer == 'undefined' || ( typeof key.layer == 'object' && key.layer[layer] ) )
					{
						k1 = k0[layer] ; for ( line in k1 )
						{
							if ( typeof key.line == 'undefined' || ( typeof key.line == 'object' && key.line[line] ) )
							{
								if ( options.syl )
								{
									k2 = k1[line] ; for ( syl = 0 ; syl < k2.length ; syl ++)
									{
										if ( typeof key.syl == 'undefined' || ( typeof key.syl == 'object' && key.syl[syl] ) )
										{
											keys.push( [ style , layer , line , syl ] ) ;
										}
									}								
								}
								else
								{
									keys.push( [style, layer , line] ) ;
								}
							}
						}

					}
				}
			}
		}

		this.addlayers.push( { key: keys , options: options } ) ;
		return this ;
	}
	
	this.arrayKeys = function( a , decnum)
	{
		var i ;
		var o = { };
		var s ;
		for ( i = 0 ; i < a.length ; i ++ )
		{
			if ( decnum ? s = Number(a[i]) : false )
			{
				a[i] -- ;
			}
			o[a[i]] = true ;
		}
		return o;
	}
	
	
	this.@l = this.@line= function( x )
	{
		var z = { };
		var y ;
		var m , i ;
		var o = { } ;
		var blend = null ;
		if ( typeof ( y = this.xml_value( x.@style ) ) != 'undefined' ? true :  typeof ( y = this.xml_value( x.@name ) ) != 'undefined' )
		{
			z.style = this.arrayKeys( [y.toLowerCase()] );
		}

		if ( typeof ( y = this.xml_value( x.@layer ) ) != 'undefined' )
		{
			z.layer = this.arrayKeys( y.match( /\d+/g ) );
		}
		if ( typeof ( y = this.xml_value( x.@line ) ) != 'undefined' )
		{
			z.line = this.arrayKeys( y.match( /\d+/g ) , 1 );
		}
		if ( this.xml_bool( x.@syl , true ) )
		{
			y = this.xml_value( x.@syl ) ;
			o.syl = true ;
			if ( m = String( y ).match( /\d+/g ) )
			{
				z.syl = this.arrayKeys( m , 1 );
			}
		}
		
		if ( this.xml_bool( x.@disabled ) )
		{
			o.disabled = true ;
		}
	
		this.l(z , o ) ;
	}

	/**
	  * @function addKara: Adds a karaoke (Text animator) to this object
	  * @note None of the karaoke has been generated until you tell it to
	  * @param animator -- An After-Effects Text animator object or name
	  * @param fixed (time fixedalized factor)
	  * @see KRKAnimator
	  * @DEPRECATED -- use adda( )
	  */
	this.addKara = function( animator , fixed )
	{
		var a ;
		var o = animator instanceof Array ? animator : [animator] ;
		for ( a in o )
		{
			this.animators[o[a]] = new KRKAnimator( o[a] , fixed ) ;
			this.animators[o[a]].layer = this ;
		}
	}
	
	
	this.timeFunction = this.defaultTimeFunction ;
	this.valueFunction = this.defaultValueFunction ;
	
	/**
	 * readPresetTimes -- read all the times from the markers
	 * @param layer -- After-Effects layer object or name (default uses this.layer)
	 * @note It stores everything to this.time
	 */
	this.readPresetTimes = function( layer )
	{
		var a, b ;
		var o = { } ;
		var markers , i ;
		var marker , makers , time , chap , comment ;
		var startTime = this.layer.startTime ;
		var stretch = parseFloat( this.layer.stretch ) / 100 ;
		var fixed = null ;
		if ( !( layer instanceof Object ) )
		{
			if ( typeof layer != 'undefined' )
			{
				fixed = layer ;
			}
			layer = this.layer ;
		}
		else if ( ! ( layer instanceof Object ) )
		{
			layer = this.comp.comp.layer( layer ) ;
		}
	
		// Setting Defaults
		o.fixed = undefined ;
		o.startTime = o.inTime = layer.inPoint ;
		o.endTime = o.outTime = layer.outPoint ;
	
		if ( ( markers = layer("Marker") ) ? markers.numKeys : false )
		{
			for ( i = 1 ; i <= markers.numKeys ; i ++ )
			{
				marker = markers.keyValue( i ) ;
				comment = marker.comment;
				this.setTimeline( comment , markers.keyTime(i) , o , i ) ;
			}
		}
		else if ( ( markers = this.comp.markers ) ? this.comp.markers.length > 0 : false )
		{
			for ( i = 0 ; i < markers.length ; i ++ )
			{
				comment = String(markers[i].comment) ;
				this.setTimeline( comment , ( parseFloat( markers[i].time ) - startTime ) * stretch , o ) ;
			}
		}
		if ( typeof fixed != 'undefined' )
		{
			o.fixed = fixed ;
		}
		this.time = o ;
		return this ;
	}
	/**
	 * set time line
	 * @param comment -- comment, etc
	 * @param time -- time
	 * @param o -- object (return value)
	 */
	this.setTimeline = function( comment , time , o , i ) 
	{
		var a;
		time = parseFloat(time);
		switch ( comment )
		{
		// fixed timing
			case '{':
				o.fixed = typeof o.fixed == 'undefined' ? '^' : true ;
		// variable timing
			case '[':
				o.startTime = time ;
				o.startMarker = i ;
			break;

			case '}':
				o.fixed = typeof o.fixed == 'undefined' ? '$' : true ;

			case ']':
				o.endTime = time ;
				o.endMarker = i ;
			break;
			
			case '<':
				o.inTime = time ;
				o.infixed = comment.match( /fixed(=(\w+))?/i ) ? true : false ;
				o.inMarker = i ;
			break;
			
			case '>':
				o.outTime =time ;
				o.outfixed = comment.match( /fixed/i ) ? true : false ;
				o.outMarker = i ;
			break;
		}
		return o;
	}
	
	/**
	 * Set time function and value function
	 * @param timeFunction -- Anonymous time function
	 * @param valueFunction -- Anonymous value function
	 */
	this.setFunctions = function( timeFunction , valueFunction )
	{
		if ( this.timeFunction )
		{
			if ( timeFunction == 'default' )
			{
				this.timeFunction = this.defaultTimeFunction ;
			}
			else
			{
				this.timeFunction = timeFunction ;
			}
		}
		
		if ( this.valueFunction )
		{
			if ( valueFunction = 'default' )
			{
				this.valueFunction = this.defaultValueFunction ;
			}
			else
			{
				this.valueFunction = valueFunction ;
			}
		}
	}

/**
 *  recursivePropertyKeysSearch( property , results )
 * Searches through the property's child to find any keyframes
 * @param results -- Array of results (output)
 */
	this.recursivePropertyKeysSearch = function( property , results )
	{
		var i = 1 ;
		var o ;
		var p = this.getProperty( this.layer , property ) ;
		if ( p.property )
		{
			for ( i = 1 ; i <= p.numProperties ; i ++ )
			{
				this.recursivePropertyKeysSearch( p.property(i) , results ) ;
			}
		}
		else if ( p.numKeys > 0 )
		{
			results.push( p ) ;
		}
	}
	this.constructor( layer , options ) ;
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

/**
 * KRKLayer( layer ) -- Main Karaoke Animator Prototype: LEVEL 4
 * @note This prototype uses KRKProperty for start, end, and offset keyframes in the range selector
 * @note2 It only applies to Text Layer.
 * @param animator -- After-Effects Text Animator object (can be numeric (index), string, or After-Effects Text Animator object)
 */
function KRKAnimator( animator , options )
{
	this.objectName = 'animator' ;
	this.parentName = 'layer' ;
	this.selectors = null ;
	this.sel = null ;
	this.start = null ;
	this.end = null ;
	this.offset = null ;
	this.array_start ;
	this.array_end;
	this.array_offset ;
	this.array_old ;
	this.animator = null ;
	this.namesAnimator ;
	this.namesKaraoke ;
	this.layer ;
	this.K ;
	this.childName = 'properties' ;
	this.caller = 'property' ;
	this.old ;
	this.dup_sel ;
	this.fixed = undefined ;
	this.currentSelector = 0 ;
	this.selectorFixed = undefined ;
	this.selectorIndex = undefined ;
	/**
	 * setUnits -- set the correct units in the selector
	 */
	this.setUnits = function( )
	{
		this.sel("Advanced")("units").setValue( 1 ) ;
	}

	/**
	 * constructor function
	 * @param animator -- After-Effects Text-Animator object (can be numeric (index), string, or After-Effects Text-Animator object)
	 * @param fixed -- fixedalized timing indication (true or false) [default is false]
	 */
	this.constructor = function( animator , options )
	{
		var i , j;
		if ( animator != undefined )
		{
			this.array_start =  [ ] ;
			this.array_end = [ ] ;
			this.array_offset = [ ] ;
			this.array_old = [ ] ;
			this.ref( animator , options ) ;
			var o = ['start' , 'end' , 'offset'] ;
			delete this.old;
			for ( j = 0 ; j < this.selectorIndex.length ; j ++ )
			{
				this.sel = this.getSelector( j ) ;
				this.array_old[j] = this.old = { } ;
				for ( i in o )
				{
					this.old[o[i]] = this[o[i]] = this['array_' + o[i]][j] = new KRKProperty( this.sel(o[i]) ) ;
					this[o[i]].parent = this ;
				}
				this.old.sel = this.sel ;
				this.old.animator = this.animator ;
				this.selectorType = this.sel.advanced.basedOn.value ;
				this.selectors = { } ;
			}
			this.sel = this.getSelector( 0 ) ;
		}
		return this ;
	}
	
	
	this.pr = function( krk )
	{
		var i ;
		var sel ;
		if ( krk instanceof Object )
		{
			sel = (krk.sel) ? krk.sel : sel ;
			var prop = ['start', 'end' , 'offset' ] ;
			for ( i in prop )
			{
				this[prop[i]].property = sel(prop[i]) ;
				this[prop[i]].animator = this ;
				this[prop[i]].layer = this.layer ;
			}
		}
		return this ;
	}
	
	this.dup = function( name )
	{
		var sel , index ;
		var $name ;
		sel = this.sel.parentProperty(this.selectorIndex[this.currentSelector]) ;
		$name = sel.name ;
		sel.enabled = false ;
		sel = sel.duplicate( );
		sel.enabled = true ;
		if ( name != undefined )
		{
			sel.name = $name + " " + name ;
		}
		sel.moveTo( index = sel.parentProperty.numProperties ) ;
		this.sel = this.getSelector( -index ) ;
		return this ;
	}
	

	/**
	 * prop( krk ) -- Resynchronize the same object
	 * @param krk -- KRKAnimator object
	 */
	this.prop = function( krk )
	{
		this.start = krk.start ;
		this.end = krk.end ;
		this.offset = krk.offset
		this.layer = krk.layer ;
	}
	
	this.getSelector = function( i , noenabled )
	{
		var sel =this.layer.layer('Text')('Animators')(this.name)('selector');
		if ( typeof i == 'undefined' )
		{
			i = this.currentSelector ;
		}
		else if ( i < 0 )
		{
			return sel(-i);
		}
		else
		{
			this.currentSelector = i ;
		}
		this.fixed = this.selectorFixed[i] ;
		this.start = this.array_start[i] ;
		this.end = this.array_end[i] ;
		this.offset = this.array_offset[i] ;
		this.old = this.array_old[i] ;
		this.sel = sel( this.selectorIndex[i] ) ;
		return this.sel ;
	}
	
	this.storeSelectors = function( )
	{
		this.selectorIndex = [ ] ;
		this.selectorFixed = [ ] ;
		var selector = this.layer.layer('Text')('Animators')(this.name)('selector') ;
		var name ;
		var sel ;
		var fixed ;
		var i , j ;
		for ( i = 1 , j = 0 ; i <= selector.numProperties ; i ++)
		{
			sel = selector(i) ;
			if ( sel.name.match( /KRK/ ) || ! sel.enabled ) { continue ; }
			this.selectorIndex[j] = i ;
		// parse the name
			this.selectorFixed[j] = this.getFixed( String(sel.name) ) ;
			j ++ ;
		}
	}

	/**
	 * ref -- reference the selector
	 * @param name -- name of the selector.  Can be an After-Effects range selector object or range selector object name
	 * @param fixed -- denote fixedalizations (default is off)
	 */
	this.ref = function( name , options )
	{
		var anim , o ;
		if ( name != undefined )
		{
			if ( anim = name instanceof Object ? name : this.layer.layer('Text')('Animators')(name) )
			{
				this.sel = anim('selector')(1) ;
				this.animator = anim ;
			}
		}
		this.syl = false ;
		this.fixed = undefined ;
		if ( options )
		{
			if ( options instanceof Object )
			{
				this.fixed = options.fixed ;
				this.syl = options.syl;
			}
			else
			{
				this.syl = options ;
				this.fixed = undefined ;
			}
		}
		this.name = this.animator.name ;
		this.storeSelectors( ) ;
		return this ;
	}

	this.shiftValueFunction = function( t , value , o )
	{
		var start = o.start == undefined ? 0 : ( o.start instanceof Array ? o.start[0] : o.start ) ;
		var end = o.end == undefined ? 0 : ( o.end instanceof Array ? o.end[0] : o.end ) ;
		return  ( value * ( end - start ) / 100 + start  ) * 100 ;
	}

	/**
	 *  uses the correct selector
	 *  selector -- After-Effects selector, can be Object, Array, or the name referenced from this.selectors
	 */
	this.uses = function( selector )
	{
		if ( selector == undefined )
		{
			return this ;
		}
		if ( selector instanceof Object )
		{
			this.sel = selector ;
		}
		else if ( selector instanceof Array )
		{
			this.sel = this.getProperty( this.layer.layer , selector ) ;
		}
		else
		{
			this.sel = this.selectors[selector] ;
		}
	}
	
	/**
	 * create -- create the text animators for the layers.  Syllable-based or whole line
	 * @param fixed -- denote fixedalizations (default is off)
	 * @return
	 */
 
	this.create = function( fixed , syllables , style2 )
	{
		var K = this.getKaraObject( ) ;
		var ks , kline , i ;
		var names = this.parseLayerName( this.layer.layer.name ) ;
		var kline = K[names.style][names.layer][names.line];
		var kline2 ;
		if ( style2 ) { kline2 = K[style2][names.layer][names.line]; }
		this.start.clearAllKeys( ) ;
		this.end.clearAllKeys( ) ;
		this.offset.clearAllKeys( ) ;
		var ksyl = kline ;
		if ( names.syl )
		{
			syllables = {  };
			syllables[names.syl] = true
		}
		if ( syllables instanceof Array )
		{
			var syl = syllables;
			syllables = { } ;
			for ( i in syl )
			{
				syllables[syl[i]] = true ;
			}
		}
		var o = typeof fixed != 'undefined' ? { fixed: fixed } : { } ;
		try{ this.old.sel.enabled = true ; } catch(e){ }
		var start = 0 ;
		this.sel = this.getSelector( ) ;
		this.start.layer = this.end.layer = this.offset.layer = this.layer ;
		var funs = { time: this.originalTimeFunction , value: this.shiftValueFunction }
		this.setUnits( ) ;
		if ( syllables != undefined ? ! ( syllables instanceof Object || syllables instanceof Array || syllables == true ) : false )
		{
			syllables = [ syllables ] ;
		}
		this.dup_sel = this.sel.parentProperty(this.sel.propertyIndex);
		this.dup_sel.enabled = false ;
		this.sel.parentProperty.parentProperty.enabled = true ;			
		if ( !( this.start.keys ? this.start.keys.length : null ) && !( this.end.keys ? this.end.keys.length : null ) &&  !( this.offset.keys ? this.offset.keys.length : null ) )
		{
			return this ;
		}
		var addspace = this.layer.spacing ? 1 : 0 ;
		var len2 ;
		
		var oldSelectorType = this.selectorType; 
/*		if ( addspace && this.selectorType == 3 )
		{
			this.selectorType = 1 ;
		}	*/
		
		if ( style2 )
		{
			var ii ;
			var kll = kline.text + "\r\n" + kline2.text ;
			start = 0 ;
			switch( this.selectorType )
			{
				case 2:
					len = kll.replace( /\s/g , '' ).length ;
					lensyl = kline.text.replace( /\s/g , '' ).length ;
					break ;
				case 3:
					for ( len = ii = 0 ; ii < 2 ; ii ++)
					{
						for ( i = lensyl = 0 ; i < (ksyl=ii?kline2:kline).length ; len += ksyl[i].text.match( /^\s*$/ ) ? 0 : 1, lensyl = !ii ? len : lensyl , i ++ ) ;
					}
					break ;
				case 4:
				case 1:
				default:
					len = kll.length + (this.layer.spacing ? kline.length + kline2.length : 0 ) - 2 ;
					lensyl = kline.text.length + (this.layer.spacing ? kline.length + kline2.length : 0 )  ;
					break ;
			}
			if ( syllables != undefined && syllables != false )
			{
				var start = 0 ;
				for ( ii = 0 ; ii < 2 ; ii ++ )
				{
					ksyl = ii ? kline2 : kline ;
					var text = '' ;
					for ( i = 0 ; i <  ksyl.length ; i ++ )
					{
						ks = ksyl[i] ;
						text = ( this.selectorType == 3 || this.selectorType == 2 ) 
							? ks.text.replace( /\s/g , '' ) : ks.text ;
						if ( ! text )
						{
							if ( this.selectorType != 3 && this.selectorType != 2 )
							{
								start += text.length + addspace ;
							}
							continue ;
						}
						o.startTime = ks.time ;
						o.endTime = ks.time + ks.dur ;
						o.start = start / len ;
						o.end = ( start + ( this.selectorType == 3 ? 1 : ( this.selectorType == 2 ? text.length : ks.text.length ) ) ) / len ; 
						o.mul = 1 ;
						if ( syllables instanceof Object ? syllables[i] : true )
						{
							this.dup( "KRK " + i ) ;
							this.start.property = this.sel('start') ;
							this.end.property = this.sel('end') ;
							this.offset.property = this.sel('offset') ;
							this.start.setKeys( o , funs ) ;
							this.end.setKeys( o , funs ) ;
							this.offset.setKeys( o , funs ) ;
						}
						start += this.selectorType == 3 ? 1 : ( this.selectorType == 2 ? text.length : ks.text.length + addspace ) ;
					}
				}
			}
			else
			{
				for ( ii = 0 ; ii < 2 ; ii ++ )
				{
					ksyl = ii ? kline2 : kline ;
					text = ( this.selectorType == 3 || this.selectorType == 2 ) 
						? ksyl.text.replace( /\s/g , '' ) : ksyl.text ;
					o.start = !ii ? 0 : o.end ;
					o.end = !ii ? ( this.selectorType == 3 ? lensyl : ( this.selectorType == 2 ? text.length : lensyl ) ) / len : 1 ; 
					o.mul = 1 ;
					this.dup( "KRK " + String(ii+1) ) ;
					this.start.property = this.sel('start') ;
					this.end.property = this.sel('end') ;
					this.offset.property = this.sel('offset') ;
					o.startTime = ksyl.start ;
					o.endTime = ksyl.end ;
					o.mul = 1 ;
					this.start.setKeys( o , funs ) ;
					this.end.setKeys( o , funs ) ;
					this.offset.setKeys( o , funs ) ;
				}
			}
		}
		else
		{
			if ( syllables != undefined && syllables != false )
			{
				var len ;
				switch( this.selectorType )
				{
					case 2:
						len = ksyl.text.replace( /\s/g , '' ).length ;
						break ;
					case 3:
						for ( i = len = 0 ; i < ksyl.length ; len += ksyl[i].text.match( /^\s*$/ ) ? 0 : 1 , i ++ ) ;
						break ;
					case 4:
					case 1:
					default:
						len = ksyl.text.length + (this.layer.spacing ? kline.length : 0 ) ;
						break ;
				}
				o.end = start = 0 ;var text ;
				for ( i = 0 ; i <  ksyl.length ; i ++ )
				{
					ks = ksyl[i] ;
					text = ( this.selectorType == 3 || this.selectorType == 2 ) 
						? ks.text.replace( /\s/g , '' ) : ks.text ;
					if ( ! text )
					{
						if ( this.selectorType != 3 && this.selectorType != 2 )
						{
							start += text.length + addspace ;
						}
						continue ;
					}
					o.startTime = ks.time ;
					o.endTime = ks.time + ks.dur ;
					o.start = start / len ;
					o.end = ( start + ( this.selectorType == 3 ? 1 : ( this.selectorType == 2 ? text.length : ks.text.length ) ) ) / len ; 
					o.mul = 1 ;
					if ( syllables instanceof Object ? syllables[i] : true )
					{
						this.dup( "KRK " + i ) ;
						this.start.property = this.sel('start') ;
						this.end.property = this.sel('end') ;
						this.offset.property = this.sel('offset') ;
						this.start.setKeys( o , funs ) ;
						this.end.setKeys( o , funs ) ;
						this.offset.setKeys( o , funs ) ;
					}
					start += this.selectorType == 3 ? 1 : ( this.selectorType == 2 ? text.length : ks.text.length + addspace ) ;
				}
			}
			else
			{
				this.dup( "KRK 1" ) ;
				this.start.property = this.sel('start') ;
				this.end.property = this.sel('end') ;
				this.offset.property = this.sel('offset') ;
				o.startTime = kline.start ;
				o.endTime = kline.end ;
				o.start = 0 ;
				o.end = 1 ;
				o.mul = 1 ;
				this.start.setKeys( o , funs ) ;
				this.end.setKeys( o , funs ) ;
				this.offset.setKeys( o , funs ) ;
			}
		}
/*		if ( addspace && this.selectorType == 3 )
		{
			this.selectorType = oldSelectorType ;
		}	*/
	}

	this.commit = function( )
	{
		var properties = [ 'start' , 'end' , 'offset' ] ;
		var i , j ;
		var prop ;
		var sel ;
		for ( j = 0 ; j < this.selectorIndex.length ; j ++ )
		{
			sel = this.sel = this.getSelector( j) ;
			for ( i in properties )
			{
				prop = properties[i] ;
				this[prop].property = sel(prop) ;
				this[prop].animator = this ;
				this[prop].layer = this.layer ;
			}
			var o = this.parseLayerName( this.layer.layer.name ) ;
			this.create( this.fixed , this.syl == undefined || this.syl == false || this.syl == null ? o.syl : this.syl , this.layer.layers2[this.layer.layer.name] ) ;
		}
		return this ;
	}

	this.constructor( animator , options ) ;
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

/**
 * KRKProperty( property ) -- Main Karaoke Property Prototype: LEVEL 4 or 5
 * @param property -- After-Effects Property object (can be numeric (index), string, or After-Effects Property object)
 */
function KRKProperty( property , options )
{
	this.parentName = 'layer' ;
	this.property = property ;
	this.properties = null ;
	this.keys ;
	this.settings ;
	this.names ; 
	this.objectName = 'property' ;
	this.childName = '' ;
	this.caller = 'property' ;
	this.layers = {} ;
	this.old = null ;
	this.unique = true ;
	this.fixed = undefined ;
	this.position ;
	this.link = null ;
	this.options = { };
	this.optionKeys = [ 'syl' , 'pos' , 'fixed' ] ;
	
	/**
	 * constructor function
	 * @param comp -- After-Effects Property object (can be numeric (index), string, or After-Effects Property object)
	 * @param options = syllable, if options is an object: syl: syllable, fixed: fixedalizations
	 */
	this.constructor = function( property , options )
	{
		if ( property != undefined )
		{
			this.setProperty( property ) ;
			this.getKeys( ) ;
			this.old = this.property ;
			this.properties = { } ; 
			var o = this.getNames( this.property ) ;
			var i;
			this.name = '' ;
			for ( i = 1 ; i < o.length ; i ++ )
			{
				this.name += ( i>1?'.':'' ) + o[i];
			}
		}
		this.is_syl = false ;
		this.options = { } ;
		if ( options )
		{
			if ( options instanceof Object )
			{
				this.fixed = options.fixed ;
				this.is_syl = options.syl;
				this.position = options.pos ;
				this.link  = options.link ;
				this.options = options ;
			}
			else
			{
				this.is_syl = options ;
			}
		}
		return this ;
	}
	
	/**
	 * setProperty -- sets the property into this.property
	 * @param property -- After-Effects property object
	 * @DEPRECATED
	 */
	this.setProperty = function( property , options )
	{
		if ( options )
		{
			if ( options instanceof Object )
			{
				this.position = options.position
			}
			else
			{
				this.position = options ;
			}
		}
		if ( property == undefined )
		{
			return this ;
		}
		else if ( property instanceof Object )
		{
			this.property = property ;
		}
		else
		{
			this.property = this.getProperty( this.layer.layer , property ) ;
		}
		
		this.getNames( ) ;
		
		return this ;
	}
	

	/**
	 * prop( krk ) -- Resynchronize the same object
	 * @param krk -- KRKProperty object
	 * @return
	 */
	this.prop = function( krk )
	{
		if ( krk.layer )
		{
			this.layer = krk.layer ;
		}
	}

	/**
	 * uses a property from the pool
	 * @param key -- the key in the pool
	 */
	this.uses = function( key )
	{
		this.layer = key instanceof Object ? key : this.layers[key] ;
	}
	
	/**
	 * pos -- sets the position by syllable on a property
	 * @param norm -- normalized factor (mul = 1/norm)
	 * @note If the current property's value is a vector of two or greater, it will also set the Y positions.
	 * @note Or else, it will only set the X positions.
	 */
	this.pos = function( norm )
	{
		var K = this.getKaraObject( ) ;
		var kline = this.obj( K , this.names , this.KRK_LINE );
		var j , l , k , ks ;
		var start , end ;
	
		if ( this.property instanceof Object )
		{
			var mul = norm ? 1/norm : 1 ;
			for ( k in this.names.length == this.KRK_SYLLABLE ? [kline[this.names[this.KRK_SYLLABLE-1]]] : kline )
			{
				ks = kline[k] ;
				property.setKeys( { mul:mul
				                  , startTime : ks.start 
				                  , endTime : ks.start + ks.dur 
				                  , start : [ks.left , ks.top] 
				                  , end : [ks.left+ks.width , ks.top+ks.height] } ) ;
			}
		}
	}
	
	/**
	 * get all of the keys in a property, and store as a variable (this.keys)
	 */
	this.getKeys = function( )
	{
		var property = this.property ;
		var keys = [ ] ;
		var i , k ;
		for ( i = 0 ; i < this.property.numKeys ; i ++ )
		{
			k = i + 1 ;
			keys[ i ] = 
			{
				  key : k
				, value :  property.keyValue( k )
				, time : property.keyTime( k )
				, interpolationType : [ property.keyInInterpolationType( k ) , property.keyOutInterpolationType( k ) ]
//				, spatialTangents : [ property.keyInSpatialTangent( k ) , property.keyOutSpatialTangent( k ) ]
				, temporalEase : [ property.keyInTemporalEase( k ) , property.keyOutTemporalEase( k ) ]
				, temporalContinuous : property.keyTemporalContinuous( k )
				, temporalAutoBezier : property.keyTemporalAutoBezier( k )
//				, roving : property.keyRoving( k )
				, selected : property.keySelected( k )
			} ;
			try{ keys[i]['spatialTangents'] = [ property.keyInSpatialTangent( k ) , property.keyOutSpatialTangent( k ) ] ; } catch( err ) { }
			try{ keys[i]['roving'] = property.keyRoving( k ) ; } catch( err ) { }
		}
		this.keys = keys ;
		this.settings =
		{
			  propertyValueType : 'propertyValueType'
			, hasMin : 'hasMin'
			, hasMax : 'hasMax'
			, minValue : 'minValue'
			, maxValue : 'maxValue'
			, isSpatial : 'isSpatial'
			, canVaryOverTime : 'canVaryOverTime'
			, isTimeVarying : 'isTimeVarying'
			, unitsText : 'unitsTime'
			, expressionEnabled : 'expressionEnabled'
			, expression : 'expression'
			, canSetExpression : 'canSetExpression'
			, expressionError : 'expressionError'
			, keyframeInterpolationType : 'keyframeInterpolationType'
		} ;
		for ( i in this.settings )
		{
			try { this.settings[i] = property[this.settings[i]] ; } catch(err) { this.settings[i] = undefined ;}
		}
		return this ;
	}
	
	/**
	 * getNames -- get the names of the proeprties
	 * @param property -- After-Effects property (default uses this.property)
	 * @return an array, and set to this.names 
	 */
	this.getNames = function( property )
	{
		if ( property == undefined )
		{
			property = this.property ;
		}
		var o = [ property.name ] ;
		var p = property ;
		var name = '' ;
		while ( p = p.parentProperty )
		{
			o.unshift( p.name ) ;
		}
		this.name = '' ;
		for ( var i = 1 ; i < o ; i ++ )
		{
			this.name += ( (i>1)?'.' : '' ) + this.names[i];
		}
		return this.names = o ;
	}
	
	/**
	 * clearAllKeys( ) -- clear all the keys in the current property
	 */
	this.clearAllKeys = function( )
	{
		var n = this.property.numKeys ;
		for( i = 1 ; i <= n ; i ++ )
		{
			try { this.property.removeKey( 1 ) } catch( err ) { return this ; }
		}
		return this ;
	}

	/**
	 * Set the keys
	 * @param options -- see valueFunction and timeFunction for details
	 * @see KRKCommon::valueFunction, KRKCommon::timeFunction
	 * @param functions -- functions object -- value: value function, time: time function (default uses presets)
	 */
	this.setKeys = function( options , functions )
	{
		var i;
		var time ;
		var key ;
		var value ;
		var t ;
		var valueFunction, timeFunction ;
		var expressionEnabled = this.property.expressionEnabled ;
		if ( ! this.keys ? null : ! this.keys.length )
		{
			return false ;
		}
		this.property.expressionEnabled = false ;		
		if ( functions != undefined )
		{
			if ( functions instanceof Object )
			{
				valueFunction = functions.value ? functions.value : this.layer.valueFunction ;
				timeFunction = functions.time ? functions.time : this.layer.timeFunction ;
				if ( functions.clear )
				{
					this.clearAllKeys( ) ;
				}
			}
			else
			{
				valueFunction = this.layer.valueFunction ;
				timeFunction = this.layer.timeFunction ;
				if ( functions )
				{
					this.clearAllKeys( ) ;
				}
			}
		}
		else
		{
			valueFunction = this.layer.valueFunction ;
			timeFunction = this.layer.timeFunction ;
		}
		options.time = this.layer.time ;
		if ( this.keys.length )
		{
			for ( i in this.keys )
			{
				key = this.keys[i];
				value = valueFunction( key.time , key.value , options ) ;
				t = timeFunction( key.time , key.value , options ) ;
				this.setKey( { value: value , time: t } , key.key ) ;
			}
		}
		else
		{
			options.mul = 1 ;
			value = valueFunction( options.startTime , options.start , options ) ;
			t = timeFunction( options.startTime , options.start , options ) ;
			this.property.setValueAtTime( t , value );
			value = valueFunction( options.endTime , options.end , options ) ;
			t = timeFunction( options.endTime , options.end , options ) ;
				this.property.setValueAtTime( t , value );
		}
		this.property.expressionEnabled = expressionEnabled ;
		return this ;
	}

	this.enableAllProperties = function( disable )
	{
		disable = disable ? true : false ;
		var p = this.property ;
		do
		{
			try { p.enabled = !disable ; } catch( err ) { }
		} while ( p = p.parentProperty ) ;
		return this ;
	}


	/**
	 * @function setKey -- set a single key
	 * @param newKey (object) -- current, new key to be set
	 * @param keyIndex -- the current key index on newKey
	 * @param names -- names -- an array of the name list; undefined to use all.
	 */
	this.setKey = function( newKey , keyIndex , names )
	{
		var i , method , name ;
		var index = null ;
		var a, b, j, k,key ;
		if ( names == undefined )
		{
			names = KRKCommon.KEYFRAMES;
		}
		if ( newKey.time != undefined )
		{
			index = this.property.addKey( newKey.time ) ;
		}
		for ( i = 0 ; i < names.length ; i ++ )
		{
			name = names[i] ;
			if ( name == 'temporalEase' && KeyframeInterpolationType.BEZIER != newKey['interpolationType'] ) { continue ; } // do not apply temporalEase if it's non-bezier interpolation.
			method = "set" + name[0].toUpperCase( ) + name.substring( 1 ) + "AtKey" ;
			key=this.keys[keyIndex-1] ;
			a = newKey[name] == undefined ? key[name] : newKey[name] ;
			if ( a == undefined ) { continue ; }
			if ( ( name != 'value' ) && ( a instanceof Array ) )
			{
				try{ this.property[method]( index != null ? index : keyIndex , a[0] , a[1] ) } catch( err ) { }
			}
			else
			{
				this.property[method]( index != null ? index : keyIndex , a ) ; 
//				try{ this.property[method]( index != null ? index : keyIndex , a ) ; } catch( err ) { throw( "Error: " + method ) }
			}
		}
		return this ;
	}
	

	
	/**
	 * Commit properties
	 * @param syl -- undefined = only line, null = all syllables, "all" = line + all syllables, numbers or array of numbers = those pertaining syllables
	 * @note -- use the default this.property to set the properties
	 */
	this.commit = function( syl )
	{
		if ( syl == undefined )
		{
			syl = this.is_syl ? this.is_syl : "line" ;
		}
		this.property = this.getProperty( this.layer.layer , this.names ) ;
		var K = this.getKaraObject( ) ;
		var dim ;
		var layerName = this.parseLayerName( this.layer.layer.name ) ;
		if ( syl == true && layerName.syl ) { syl = layerName.syl ; }
		var k = K[layerName.style][layerName.layer][layerName.line] ;
		var ks ;
		var options = typeof this.fixed != 'undefined' ? { fixed: this.fixed } : { } ;
		this.enableAllProperties( ) ;
		
		// Check for the links
		if ( this.link && ( this.link instanceof Object ) || ( typeof this.link == 'string' ) )
		{
			var c , c2 ;

			var layerStuff = this.layerNaming( 
					  this.link.length < 3 ? this.link[0] : this.link[1]
					, layerName.style
					, layerName.layer
					, layerName.line
					, layerName.syl ) ;
			var e , pr ;
			var layerStuff2 = this.layerNaming( 
					  this.link.length < 3 ? this.link[0] : this.link[1]
					, layerName.style
					, layerName.layer
					, layerName.line ) ;
			var c ;
					if ( this.link.length > 2 ? ( c = this.layer.comp.project.comps[this.link[0]].comp ) : null )
					{
						try
						{
							if ( pr = eval( "c.layer(layerStuff)." + this.link[2] ) )
							{
								this.property.expression = 'comp("' + this.link[0] + '").layer("' + layerStuff + '").' + this.link[2] ;
								this.property.expressionEnabled = true ;
							}
						}
						catch( err )
						{
							try
							{
								if ( pr = eval( "c.layer(layerStuff2)." + this.link[2] ) )
								{
									this.property.expression = 'comp("' + this.link[0] + '").layer("' + layerStuff2 + '").' + this.link[2] ;
									this.property.expressionEnabled = true ;
								}
							}
							catch( err )
							{
							}
						}
					}
					else if ( this.layer.comp.layers[this.link[0]] )
					{
						try
						{
							c = this.layer.comp.comp.layer(layerStuff) ;								
							if ( pr = eval( "c." + this.link[1] ) )
							{
								this.property.expression = 'thisComp.layer("' + layerStuff + '").' + this.link[1] ;
								this.property.expressionEnabled = true ;
							}
						}
						catch( err )
						{
							try
							{
								c = this.layer.comp.comp.layer(layerStuff2) ;								
								if ( pr = eval( "c." + this.link[1] ) )
								{
									this.property.expression = 'thisComp.layer("' + layerStuff2 + '").' + this.link[1] ;
									this.property.expressionEnabled = true ;
								}
							}
							catch( err )
							{
							}
						}
					} 
			return this ;
		}
	
		if ( syl && syl != 'line' )
		{
			var i , s , t ;
			if ( layerName.syl )
			{
				if ( syl==true || syl == "all" )
				{
					s = [k[layerName.syl]] ;
				}
				else
				{
					s = syl instanceof Array ? syl : [syl];
				}
				this.clearAllKeys( ) ;
/*				if ( this.position && this.property.expression && this.property.canSetExpression )
				{
					var exp = this.property.expression ;
					exp = exp.replace( /(\bvar\s+)?\b[o]\s*=\s*[^;\n]* /g , '' ) ;  // Remove any user-defined widths and heights for tests.
					this.property.expression = "var o={w:" + k.width + ",h:" + "h=" + k.height + "};\n\n" + exp;
					this.property.expressionEnabled = true ;
				}*/
			}
			else
			{
				if ( syl==true || syl == "all" )
				{
					s = k ;
				}
				else
				{
					s = syl instanceof Array ? syl : [syl];
				}
				this.clearAllKeys( ) ;
/*				if ( this.position && this.property.expressionEnabled && this.property.canSetExpression )
				{
					var exp = this.property.expression ;
					exp = exp.replace( /(\bvar\s+)?\b[o]\s*=\s*[^;\n]* /g , '' ) ;  // Remove any user-defined widths and heights for tests.
					this.property.expression = "var o={w:" + k.width + ",h:" + "h=" + k.height + "};\n\n" + exp;
				}*/
			}
			var style_2 ;
			if ( style_2 = this.layer.layers2[this.layer.layer.name] )
			{
				var ii ;
				for ( ii = 0 ;  ii < 2 ; ii ++ )
				{
					k = ii ? K[style_2][layerName.layer][layerName.line]
					         : K[layerName.style][layerName.layer][layerName.line] ;
							 
					for ( i=0 ; i < k.length ; i ++ )
					{
						ks = k[i] ;
						options.mul = options.start = options.end = null ;
						options.startTime = ks.time ;
						options.endTime = ks.time + ks.dur ;
						this.setKeys( options ) ;
					}
				}
			}
			else
			{
				if ( this.position )
				{
					var pi , pos , basedOn ;
					if ( ! ( this.position instanceof Array ) )
					{
						this.position = [this.position] ;
					}
					for ( pi = 0 ; pi < this.position.length ; pi ++ )
					{
						pos = this.position[pi];
						if ( basedOn = this.layer.comp.layers[pos] )
						{
							basedOn = basedOn.name ;
							try {
							if ( dim = this.layer.getDim( this.layerNaming( basedOn , layerName.style , layerName.layer , layerName.line , layerName.syl ) ) )
							{
								break ;
							} } catch( err ) { }
						}
					}
					try
					{
						if ( !dim )
						{
							dim = this.layer.getDim( this.layerNaming( this.layer.name , layerName.style , layerName.layer , layerName.line , layerName.syl ) )
						}
					} catch( err ) { }
				}

				for ( i=0 ; i < s.length ; i ++ )
				{
					if ( syl == true || syl == "all" )
					{
						t = i ;
						ks = s[t] ;
					}
					else
					{
						t = s[i] ;
						ks =k[t] ;
					}
					if ( this.position && dim && dim[i] )
					{
						options.mul = 1.0/100;
						options.start = [dim[i].left , dim[i].top] ;
						options.end = [dim[i].left+dim[i].width , dim[i].top+dim[i].height] ;
					}
					else
					{
						options.mul = options.start = options.end = null ;
					}
					options.startTime = ks.time ;
					options.endTime = ks.time + ks.dur ;
					this.setKeys( options ) ;
				}
			}
		}
		if ( !syl || syl == 'line' || syl == 'all' )
		{
				options.mul = options.start = options.end = null ;
				options.startTime = k.start ;
				options.endTime = k.end ;
				this.clearAllKeys( ) ;
				this.setKeys( options ) ;
		}
	
	
	/******************************************
		Set the options
	/*****************************************
		var opt ;
		for ( opt in this.options )
		{
			if ( this.property[opt] != undefined )
			{
				try { this.property[ opt ] = this.options[opt] ; }
				catch( err )
				{
					this.property[opt](this.options[opt]) ;
				}
			}
		}
		/**** WON'T WORK *************/
	}
	this.constructor( property , options ) ;
}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

/**
 * KRKCommon( ) -- Main Karaoke Common Prototype: LEVEL 0
 * @note This prototype contains base functions for all of other prototypes.
 *       This way, no extra codes need to be rewritten.
 */
function KRKCommon( )
{
	
	/**
	 * resets the children
	 */
	this.reset = function ( )
	{
		this[this.childName] = this.unique ? { } : [ ] ;
		return this;
	}
	
	/**
	 * newKRK -- New Karaoke Object
	 * @param o -- Initializer Object
	 * @param childName -- override Child name
	 * @param extraparam -- one extra parameter
	 * @return the new KRK Object
	 */
	this.newKRK = function( o , childName , extraparam )
	{
		if ( !childName ) { childName = this.childName ; }
		var obj ;
		switch ( childName )
		{
			case "projects":
				obj = new KRKProject( ) ;
				break;
			case "comps":
				obj = new KRKComp( ) ;
				break;
			case "layers":
				obj = new KRKLayer( ) ;
				break;
			case "properties":
				obj = new KRKProperty( ) ;
				break;
			default:
				obj =new KRKAnimator( ) ;
		}
		obj[this.objectName ? this.objectName : 'parent' ] = this ;
		o == null ? obj.constructor( ) : ( extraparam != undefined ? obj.constructor( o , extraparam ) : obj.constructor( o ) ) ;
		return obj ;
	}
	
	/**
	 * add( object )
	 * @param object -- array, object, scalar of object names or objects
	 * @return scalar, array, object of the object type
	 */
	this.add = function( object , extraparam , childName )
	{
		var c = childName ? childName : this.childName ;
		var name , obj ;
		if ( object instanceof Array )
		{
			var o , objects ;
			objects = [ ] ;
			for ( o in object )
			{
				objects[o] = this.add( object[o] , extraparam , childName ) ;
			}
			return objects ;
		}
		else
		{
			if ( ! ( this[c] instanceof Array ) && ! ( this[c] instanceof Object ) )
			{
				this.reset( ) ;
			}
		
			if ( object instanceof Object )
			{
				if ( object.caller != undefined )
				{
					return this[c][ this[c] instanceof Object ? o.name : this[c].length ] = 
						object ;					
				}
				name = object.name ;
				obj = object ;
			}
			else
			{
				name = object ;
				obj = name ;
			}
		
			name = object instanceof Object ? object.name : object ;
			var o = this.newKRK( obj , c , extraparam ) ;
			this[c][ this[c] instanceof Object ? o.name : this[c].length ] = o ;
			return o ;
		}
	}
	
	/**
	 * deletes at least one object
	 * @param object -- an index or the object of the objects
	 * @param number -- number of objects to be deleted
	 * @return
	 */
	this.del = function( object , number )
	{
		var i ;
		var j = false ;
		if ( object instanceof Object )
		{
			for ( i in this[this.childName] )
			{
				if ( this[this.childName][i] == object )
				{
					j = 1 ;
					break ;
				}
			}
			if ( ! j )
			{
				return null ;
			}
		}
		else
		{
			i = object ;
		}
		var o = this[this.childName].splice( i , number == undefined ? 1 : number ) ;
		for ( var obj in o )
		{
			delete obj ;
		}
		return this ;
	}
	
	/**
	 * Get the Karaoke JSON
	 * @note requires KRKProject to be on the top.
	 * @return Karaoke JSON
	 */
	this.getKaraObject = function( )
	{
		var o = this.getParent("project") ;
		return o ? o.K : this.K ;
	}
	
	/**
	 * Get the parent
	 * @param name -- name of the object (KRK[name])
	 * @return parent
	 */
	this.getParent = function( name )
	{
		name = name == undefined ? null : name.toLowerCase() ;
		switch( this.objectName )
		{
			case "project":
				switch( name )
				{
					case "project":
						return this ;
					default:
						return null ;
						break;
				}
			case "comp":
				switch (name)
				{
					case "project":
					case null:
						return this.project ;
					case "comp":
						return this ;
				}
				break;
			case "layer":
				switch (name)
				{
					case "project":
						return this.comp.project ;
					case "comp":
					case null:
						return this.comp ;
					case "layer":
						return this ;
				}
				break;
			case "animator":
			case "property":
				switch (name)
				{
					case "project":
						return this.layer.comp.project ;
					case "comp":
						return this.layer.comp ;
					case "layer":
					case null:
						return this.layer ;
					case "property":
						return this ;
				}
				break;
		}
		return null ;
	}
	
	/**
	 * Get the child
	 * @param i -- index of the child
	 * @return the child object
	 */	
	this.getChild = function( i )
	{
		return i == undefined ? this[childName] : this[childName][i] ;
	}
	
	/**
	 * parseLayerName -- parse the layer name
	 * @param name -- After-Effects Layer Name -- format KRK[Style][0][1] 2
	 * @param K -- Karaoke JSON (Optional)
	 * @return an object of the following form:
	 *         names: type, basedOn, style, layer, line, syl
	 *         line: karaoke line JSON
	 *         syl: Karaoke syllable JSON
	 */
	this.parseLayerName = function( name , K )
	{
		var line , names ;
		if ( ( name == undefined ) || name == null )
		{
			name = this.layer.name ;
		}
		else if ( name instanceof Object )
		{
			name = name.name ;
		}		
		if ( a = name.match( /^(.+?)\[\s*(.*?)\s*\]\[\s*(.*?)\s*\]\[\s*(.*?)\s*\](\[\s*(.*?)\s*\])?\s*([^\[\]]*\s*$)/ ) )
		{
			names = { basedOn: a[1], style: a[2], layer: a[3], line: a[4], syl: a[6], extra: a[7] } ;
			if ( K )
			{
				line = K[a[2]][a[3]][a[4]] ;
				kara = a.length < 7 ? null : K[a[2]][a[3]][a[4]][a[6]] ;
			}
		}
		else
		{
			return null ;
		}
		return K ? { names: names , line: line , syl: kara } : names ;
	}
	
	/**
	 * originalTimeFunction -- obtains the value
	 * @param value (not used)
	 * @param t time
	 * @param o for options
	 */	
	this.originalTimeFunction = function( t , value , o )
	{
		var that = o['time'] ? o['time'] : this.time ;
		
		var fixed = undefined ;
		var startTime , endTime ;
/*		if ( typeof o.fixed == 'undefined' )
		{
			o.fixed = that.fixed ;
		}*/
		if ( o.fixed == '^' )
		{
			return t + o.startTime - that.startTime ;
		}
		if ( o.fixed == '$' )
		{
			return t + o.endTime - that.endTime ;
		}
		fixed = typeof o.fixed != 'boolean' ? parseInt( o.fixed ) : ( fixed ? 0 : undefined ) ;	
		if ( fixed === 0 || fixed )
		{
			return t + o.startTime - that.startTime + ( o.endTime - o.startTime ) * fixed * 0.01 ;
		}
		else
		{
			t = ( t - that.startTime ) / ( that.endTime - that.startTime ) ;
			return t * ( o.endTime - o.startTime ) + o.startTime ;
		}		
	}
	
	/**
	 * originalValueFunction -- obtains the value
	 * @param value 
	 * @param t time (not used)
	 * @param o for options
	 */
	this.originaValueFunction = function( t , value , o )
	{
		var mul = o.mul == undefined ? 1 : o.mul ;
		if ( value instanceof Array )
		{
			var opt = [ ] ;
			var start, end ;
			for ( i in value )
			{
				start = o.start == undefined ? 0 : ( o.start instanceof Array ? o.start[i] : o.start ) ;
				end = o.end == undefined ? 1 : ( o.end instanceof Array ? o.end[i] : o.end ) ;
				opt.push( o.fixedValue ? mul * value[i] : ( value[i] * mul * ( end - start ) + start ) );
			}
			return opt ;
		}
		else
		{
				start = o.start == undefined ? 0 : ( o.start instanceof Array ? o.start[0] : o.start ) ;
				end = o.end == undefined ? 1 : ( o.end instanceof Array ? o.end[0] : o.end ) ;
				opt.push( o.fixedValue ? mul * value[0] : ( value[0] * mul * ( end - start ) + start ) );
		}
	}


	/**
	 * Default time function (time is karaoke-time)
	 * @param t -- time
	 * @param value -- value [place holder]
	 * @param o -- an object of this format -- start: starting value, end: ending value,
	 *                                         startTime: starting time, endTime: ending time,
	 *                                         fixed: "start" fixed from the start time, "end": fixed from the end time, default is normalized
	 * @return adjusted time
	 */
	this.defaultTimeFunction = function( t , value , o )
	{
		var that = o['time'] ? o['time'] : this.time ;
		var fixed = undefined ;
		var startTime , endTime ;
/*		if ( typeof o.fixed == 'undefined' )
		{
			o.fixed = that.fixed ;
		}*/
		if ( o.fixed == '^' )
		{
			return t + o.startTime - that.startTime ;
		}
		if ( o.fixed == '$' )
		{
			return t + o.endTime - that.endTime ;
		}
		fixed = typeof o.fixed != 'boolean' ? parseInt( o.fixed ) : ( fixed ? 0 : undefined ) ;
		if ( fixed === 0 || fixed )
		{
			return t + o.startTime - that.startTime + ( o.endTime - o.startTime ) * fixed * 0.01 ;
		}
		else
		{
			t = ( t - that.startTime ) / ( that.endTime - that.startTime ) ;
			return t * ( o.endTime - o.startTime ) + o.startTime ;
		}
	}

	/**
	 * Default value function
	 * @param t -- time
	 * @param value -- value [place holder]
	 * @param o -- an object of this format -- start: starting value, end: ending value,
	 *                                         startTime: starting time, endTime: ending time,
	 *                                         fixed: "start" fixed from the start time, "end": fixed from the end time, default is normalized
	 * @return adjusted value
	 */
	this.defaultValueFunction = function( t , value , o )
	{
		var mul = o.mul == undefined ? 1 : o.mul ;
		if ( value instanceof Array )
		{
			var opt = [ ] ;
			var start, end ;
			for ( i in value )
			{
				start = o.start == undefined ? 0 : ( o.start instanceof Array ? o.start[i] : o.start ) ;
				end = o.end == undefined ? 1 : ( o.end instanceof Array ? o.end[i] : o.end ) ;
				opt.push( o.fixedValue ? mul * value[i] : ( value[i] * ( end - start ) * mul + start ) );
			}
			return opt ;
		}
		else
		{
			var start = o.start == undefined ? 0 : ( o.start instanceof Array ? o.start[0] : o.start ) ;
			var end = o.end == undefined ? 1 : ( o.end instanceof Array ? o.end[0] : o.end ) ;
			return typeof o.fixedValue == 'undefined' || o.fixedValue ? ( mul == 1 ? value :  mul * value ) : ( value * ( end - start ) * mul + start ) ;
		}
	}


	/**
	 * layer naming
	 * @param title -- title
	 * @param basedOn -- name, based on the layer
	 * @param style -- Karaoke JSON style name
	 * @param layer -- Karaoke JSON layern umber
	 * @param line -- Karao ke JSON line number
	 * @param syl -- Karaoke JSON syllable number
	 * @return
	 */
	this.layerNaming = function( basedOn , style , layer , line , syl , extra )
	{
		if (!this.K) { this.K = this.getKaraObject( ) ; }
		syl = syl == undefined ? "" : " " + syl ;
		return String( basedOn ) + '[' + String( style ) + '][' + String( layer ) + '][' + String( line ) + ']' + ( syl!=undefined && syl != '' && syl!=null ? '[' + String( syl.replace( /\s+/ , '' ) ) + ']' : '' ) + ( extra != undefined ? String(extra) : '' ) ;
	}
	
	/**
	 * Get the After-Effects property
	 * @param layer -- After-Effects layer object
	 * @param names -- Array of the names to be propagated through.
	 * @return
	 */
	this.getProperty = function( layer , names )
	{
		var o , i , j , p , name1 , more ;
		o = layer ;
		if ( names instanceof Array )
		{
			for ( i in names )
			{
				if ( p = o.property(names[i]) ) { o = p ; }
			}
		}
		else if ( names instanceof Object )
		{
			return names ;
		}
		else
		{
			try
			{
				eval( "if ( p = o." + names + " ) { o = p ; }" )
			}
			catch( err )
			{
				return null ;
			}
		}
		return o ;
	}
	
	/**
	 * sets all the variables in the parent or children to be a value
	 * @param key -- key of the variable
	 * @param value -- value to be set
	 * @param all -- if set, it will set all the children, if not set, it will set the current property
	 *               undefined -- set all children
	 *               null or 'old' -- set the original object
	 *               otherwise -- set the current object
	 */
	this.set = function( key , value , all )
	{
		var o = all == undefined ? [this[this.objectName]] : ( all != null && all != 'old' ? this[this.childName] : [this.old] ) ;
		var i;
		for ( i in o )
		{
			o[i][key] = value ;
		}
		return this ;
	}
	
	/**
	 * Performs a function in all of the children
	 * @param functionName -- function name (String)
	 * @param args -- Arguments: currently -- can only have up to 8 arguments
	 */
	this.call = function( functionName , args )
	{
		var o ;
		for ( i in this[this.childName] )
		{
			o = this[this.childName][functionName] ;
			if ( args == undefined ) { args = [ ] ; } ;
			switch ( args.length )
			{
				case 0:
					o( ) ;
					break ;
				case 1:
					o( args[0] ) ;
					break ;
				case 2:
					o( args[0] , args[1] ) ;
					break ;
				case 3:
					o( args[0] , args[1] , args[2] ) ;
					o( args[0] , args[1] , args[2] ) ;
					break ;
				case 4:
					o( args[0] , args[1] , args[2] , args[3]) ;
					break ;
				case 5:
					o( args[0] , args[1] , args[2] , args[3] ,args[4] ) ;
					break ;
				case 6:
					o( args[0] , args[1] , args[2] , args[3] ,args[4] , args[5] ) ;
					break ;
				case 7:
					o( args[0] , args[1] , args[2] , args[3] ,args[4] , args[5] , args[6] ) ;
					break ;
			}
		}
		return this ;
	}
	
	/**
	 * Set the name of the current object
	 * @param name -- name
	 */
	this.setName = function( name )
	{
		this.name = name ;
		return this ;
	}
	
	/**
	 * Push elements into an array
	 * @param res -- original array
	 * @param res1 -- elements to be pushed
	 * @param name -- (optional) extra one element to be appended
	 * @return res
	 */
	this.pushArray = function( res, res1 , name )
	{
		var o = [ ] ;
		if ( res1 )
		{
			for ( var i in res1 )
			{
				o.push( res1[i] ) ;
			}
		}
		if ( name ) { o.push( name ) ; }
		res.push( o ) ;
		return res; 
	}
	
	/**
	 * recurse through the object with your names
	 * @param object
	 * @param level -- level number (will stop when this level is reached)
	 * @param names: A JSON representation: Example --
	 *               null for everything
	 *               ['romaji'], { romaji: null } for only romaji
	 *               { romaji: [0,1] } for only romaji and Layer #0 and #1
	 *               { romaji: {0: [0,1,2,3:[3,4,5]]} }, etc
	 * @param res (internal -- not set)
	 * @param res1 (internal -- not set)
	 * @return an array list of the names to the object (from level 1 to level level)
	 */
	this.recurseKaraoke = function( level , names )
	{
		var K = this.getKaraObject( ) ;
		var a, b, c, d, e ;
		var na , nb ,nc , nd ;
		var Ka , Kb , Kc , Kd ;
		var pushes = [ ] ;
		if ( !names )
		{
			names = [ ] ;
		}
		else if ( ! ( names instanceof Array ) )
		{
			var n = names.split( /\s*,\s*/ ) ;
			names = n.length < 2 ? [ names ] : n ;
		}
		
		for ( a in names )
		{
			if ( ! ( names[a] instanceof Array ) )
			{
				var n = names[a].toLowerCase() ;
				names[a] = {  } ;
				names[a][n] =n  ;
			}
			else
			{
				var n = { } ;
				for ( b in names[a] )
				{
					n[names[a][b].toLowerCase()] = names[a][b] ;
				}
				names[a] = n  ;
			}
		}
		var x,y,z,w;
		for ( a in na = names.length < 1 ? K : names[0] )  // Style
		{
			a = a.toLowerCase() ;
			x = names.length < 1 ? a : na[a] ;
			x = x.toLowerCase( ) ;
			Ka = names.length < 1 ? na[x] : K[x] ;
			for ( b in nb = names.length < 2 ? Ka : names[1] ) // Layer
			{
				y = names.length < 2 ? b : nb[b] ;
				if ( y != Number(y) ) { continue ; }
				Kb = names.length < 2 ? nb[y] : Ka[y] ;
				for ( c in nc = names.length < 3 ? Kb : names[2] ) // Line Number
				{
					z = names.length < 3 ? c : nc[c] ;
					if ( z != Number(z) ) { continue ; }
					Kc = names.length < 3 ? nc[z] : Kb[z] ;
					if ( level == 3 )
					{
						pushes.push( [ x , y , z ] ) ;
						continue ;
					}
					for ( d in nd = names[3] < 4 ? Kc : names[3] ) // Syllable Number
					{
						w = names.length < 4 ? d : nd[d] ;
						if ( w != Number(w) ) { continue ; }
						if ( level >= 4 )
						{
						pushes.push( [ x , y , z , w ] ) ;
							continue ;
						}
					}
				}
			}
			return pushes ;
		}
	}

	/**
	 * objects -- returns a list of objects
	 * @param object -- object
	 * @param names -- an array of array, call from recurseObject
	 * @param level -- the set level
	 * @return an array of objects
	 * @see KRKCommon::recurseObject
	 */
	this.objects = function( object , names , level )
	{
		var name , i , j , o , obj ;
		fobj = [ ] ;
		for ( i in names )
		{
			name = names[i] ;
			if ( level != undefined )
			{
				if ( name.length < level )
				{
					continue ;
				}
			}
			o = object ;
			for ( j in name )
			{
				try{ o = o[name[j]] } catch(err) { break ; }
			}
			obj.push( o ) ;
		}
		return obj ;
	}
	
	/**
	 * addObject -- add a content to the object
	 * @param object -- object
	 * @param key -- an array of keys to be added
	 * @param content -- content to be added
	 * @return object
	 */
	this.addObject = function( object , key , content )
	{
		var i ;
		var k ;
		var o = object ;
		for ( i in key )
		{
			k = key[i] ;
			if ( key.length == i + 1 )
			{
				o[k] = content ;
			}
			else if ( ! ( o[k] instanceof Object ) )
			{
				o[k] = { } ;
			}
			o = o[k] ;
		}
		return object ;
	}
	
	/**
	 * obj - Get an object
	 * @param object
	 * @param arrayKey: an array key, can be from recurseObject
	 * @param level
	 * @see KRKCommon::recurseObject, KRKCommon::objects
	 * @return
	 */
	this.obj = function( object , arrayKey , level )
	{
		var o = object ;
		var key ;
		if ( level == undefined ) { level = 0 ; }
		for ( i in arrayKey )
		{
			key = arrayKey[i] ;
			try { o = o[key] } catch( err ) { return null ; }
			if ( level && i + 1 == level )
			{
				return o ;
			}
		}
		return o ;
	}
	
	/**
	 * reverts to the old state (i.e. this[object] = this.old)
	 */
	this.revert = function( )
	{
		var i ;
		if ( this.old instanceof Object )
		{
			for ( i in this.old )
			{
				this[i] = this.old[i] ;
			}
		}
		else
		{
			this[objectName] = this.old ;
		}
		return this ;
	}
	
	/**
	 * enables the original object
	 * @param all -- true to enable all objects in the pool
	 */
	this.enable = function( all )
	{
		this.set( 'enable' , true , all ) ;
	}

	/**
	 * Disables the original object
	 * @param all -- true to enable all objects in the pool
	 */
	this.disable = function( all )
	{
		this.set( 'enable' , false , all ) ;
	}

	/**
	 * Get an After-Effects Object (KRK BASE)
	 * @param name -- name of the object (can be object, string, or number)
	 * @note if it gives a null or returns an error, it will perform a case-insensitive search until it gets the right name
	 */
	this.getAObject1 = function( name )
	{
		var o ;
		name = name.toLowerCase() ;
		var i = 1;
		do
		{
			try{ o = this[this.objectName][this.caller](i++) } catch( err ) { return null ; }
			if (! ( o instanceof Object ) ) { return null ; }
			if ( o.name ? o.name.toLowerCase( ) == name : false )
			{
				return o ;
			}
		} while ( o ) ;
	}

	this.getAObject = function( name )
	{
		var obj ;
		if ( name instanceof Object )
		{
			this[this.objectName] = name ;
		}
		else
		{
			try
			{
				if ( obj = this[this.objectName][this.caller](name) )
				{
					return obj ;
				}
				else
				{
					return this.getAObject1( name ) ;
				}
			}
			catch( err )
			{
				return this.getAObject1( name ) ;
			}
		}
	return null ;
	}
}

KRKCommon.prototype.xml_value = function( x )
{
	var s = x.toString( ) ;
	if ( s == '' )
	{
		return undefined ;
	}
	else
	{
		return s;
	}
}

KRKCommon.prototype.xml_bool = function( x , nonzero )
{
	if ( typeof x == 'xml' )
	{
		x = x.toString( ) ;
	}
	if ( typeof x == 'string' )
	{
		if ( x.match( /^\s*$/ ) )
		{
			return undefined ;
		}
		if ( x.match( nonzero ? /^\s*(none|no|false|non|nothing|not|\!|\~|void)\s*/gi : /^\s*(none|no|0+|false|non|nothing|void)\s*/gi ) )
		{
			return false ;
		}
		else
		{
			return true ;
		}
	}
	return undefined ;
}


KRKCommon.prototype.error = function( new_err , old_err )
{
	var i ;
	for ( i in old_err )
	{
		if ( typeof new_err[i] == 'undefined' )
		{
			new_err[i] = old_err[i] ;
		}
	}
	throw new_err ;
}

KRKCommon.KEYFRAMES = [	  "value"
					, "interpolationType"
					, "spatialTangents"
					, "temporalEase"
					, "temporalContinuous"
					, "spatialContinuous"
					, "roving"
					, "selected"
					, "temporalAutoBezier"
					, "spatialAutoBezier" ]  ;


KRKCommon.prototype.KRK_SYLLABLE = 4 ;
KRKCommon.prototype.KRK_LINE = 3 ;

KRKCommon.prototype.getFixed = function( value )
	{
		var fixed = this.xml_bool( value , true );
		var fixed2 = this.xml_value( value ) ;
		var fixed3 ;
		if ( fixed && fixed2 )
		{
			fixed2 = fixed2.toLowerCase( );
			
			if ( fixed2 == 'start' || fixed2.match( /\^/ ) )
			{
				fixed = '^' ;
			}
			else if ( fixed2 == 'end' || fixed2.match( /\$/ ) )
			{
				fixed = '$'
			}
			else if ( fixed3 = fixed2.match( /-?\d+\%/ ) )
			{
				fixed = parseInt( fixed3[0] ) ;
			}
			else if ( ! isNaN( fixed3 = parseInt( fixed2 ) ) )
			{
				fixed = fixed3 ;
			}
			else
			{
				fixed = undefined ;
			}
		}
		return fixed ;
	}

//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///        GUI
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

function KRKGUI () 
{
	this.project = new KRKProject( ) ;
	this.run = function( )
	{
		try
		{
			if ( this.project.@codes( ) )
			{
				this.project.begin( ) ;
				if ( confirm( 'It seems to be good to go!!\nThe generated comps and layers also have been purged.\n\nDo you want to commit your settings and generate the layers?\n\nNote: It may take a few minutes to generate depending upon how many layers and properties required to be generated.' ) )
				{
					this.project.commit( ) ;
					this.project.topLayers( ) ;
				}
				this.project.end( ) ;
			}
			else if ( typeof krkx == 'undefined' )
			{
				alert( 'Possible Reasons of Failure:\n\n1.  "KRK" Composition is not found.\n2.  You have not added any compositions in KRK Comp,\n3.  Make sure at least one comp layer in "KRK" comp is NOT SHY.\n4.  You have not provided the karaoke .ass paste in the topmost text layer.' ) ;
			}
			this.project.destruct( ) ;
		} 
		catch( e )
		{
			this.error_alert( e ) ;
		}
	}
}

KRKGUI.prototype = new KRKCommon( ) ;

// Error alert
KRKGUI.prototype.error_alert = function( e )
{
	var syntax =
	{
		"message": "" ,
		"comp": "Comp: " ,
		"layer": "Layer: " ,
		"animator": "Animator: " ,
		"property": "Property: " ,
		"setting": "Setting: ",
		"kara": "Karaoke: " ,
		"number": "Error Code: " ,
		"line": "KRK's Line #" ,
		"name": "Name: " ,
		"description": "Description: " ,
		"xml": "--== XML ==--\n"
	} ;
	var $string , i , j , text , $text ; 
	if ( e instanceof Object )
	{
		$string = '' ;
		for ( i in syntax )
		{
			text = e[i] ;
			if ( typeof text != 'undefined' )
			{
				if ( $string !== '' )
				{
					$string += "\n" ;
				}
				if ( text instanceof Array )
				{
					$text = text ;
					text = '' ;
					for ( j = 0 ; j < $text.length ; j ++ )
					{
						if ( text !== '' ) { text += ', ' ; }
						text += $text[j] ;
					}
				}
				else if ( typeof text == 'xml' )
				{
					text = text.toXMLString( ) ;
				}
				else if ( text instanceof Object )
				{
					$text = text ;
					text = '' ;
					for ( j = 0 ; j < $text.length ; j ++ )
					{
						if ( text !== '' ) { text += ', ' ; }
						text += j + ": " + $text[j] ;
					}
				}
				$string += syntax[i] + text ;
			}
		}
		if ( $string !== '' )
		{
			alert($string + "\n(This message is copied to KRK's text layer's comment)");
			if ( this.project && this.project.krkText )
			{
				this.project.krkText.comment = "LAST ERROR MESSAGE:\n" + $string ;
			}
		}
	}
}
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////




/* Inheritance Levels */
KRKProject.prototype = new KRKCommon( ) ;
KRKComp.prototype = new KRKCommon( ) ;
KRKLayer.prototype = new KRKCommon( ) ;
KRKAnimator.prototype = new KRKCommon( ) ;
KRKProperty.prototype = new KRKCommon( ) ;








/** AUTO ADD **/

KRKGUI.instance = new KRKGUI( ) ;
KRKGUI.instance.run( ) ;

/************************************************************************
VERSION TRACKING
0.63a: XML Configurations.
0.56: Fixed vertical auto-spacing text
0.55: Fixed more bugs (Syllables per layer, text animators with correct spaces)
0.54: Fixed properties linking, do spaces on text animators
0.53: Forces an average for the SPACE widths/heights, since they're 0.
0.52: Minor mistakes
0.51: Added Property linking .p( "property" , {link: ['comp','layer','property'] ) VIA expressions <comp is optional>
0.50: You can now insert .ass into your project
      Auto-word tracking is implemented--for per-syllable effects
      Auto-positioning is implemented using the layer
0.46: Fixed a couple of bugs.
0.45: Added Syllable per layer to line timing: longer effects.
0.44: Fixed text animators for syllable per layer.
      Added Karaoke text as markers on the generated layer for debugging purpose
0.43: Fixed Line timing for two-lined effect (still broken--due to how ranges are added)
      Recursive Property names search
      Added some newbie error checks.
0.42: Added Slider control for syllable spacing,
      In-composition scripting
      Correctly generated text animators using basedOn property.
0.41: Added Autodeletion and Correct markers of generated layers
0.40:  Embeddable scripts
0.30:  Complete objects redesigns
0.20:  Revitalize the script changes
0.1a: Changed to all associative arrays.
0.10:  Initial script
************************************************************************/ 